Support JSON response mode for StreamableHTTPTransport
## Motivation and Context The MCP Streamable HTTP specification allows servers to return POST responses as either `text/event-stream` (SSE) or `application/json`: > If the input is a JSON-RPC request, the server MUST either return `Content-Type: text/event-stream`, > to initiate an SSE stream, or `Content-Type: application/json`, to return one JSON object. See: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server The TypeScript and Python SDKs support a configurable JSON response mode via `enableJsonResponse` / `is_json_response_enabled`. JSON response mode is suitable for simple tool servers that do not need server-initiated requests. It avoids SSE framing overhead and returns a single JSON object for the POST response. ## Behavior - POST responses use `Content-Type: application/json` and return a single JSON object. - The POST `Accept` header requirement is relaxed to `application/json` only (matching the Python SDK's lenient behavior). - Request-scoped notifications (`progress`, `log`) cannot ride along with the single-object response and are silently dropped. - Session-scoped standalone notifications (`resources/updated`, `elicitation/complete`) and broadcast notifications (`tools/list_changed`, etc.) continue to flow to clients connected to the GET SSE stream. - All server-to-client requests (`sampling/createMessage`, `roots/list`, `elicitation/create`) raise an error in JSON response mode. - Combines with `stateless: true` for simple single-response servers without session tracking. ## How Has This Been Tested? Added tests for JSON response mode: - POST request returns `application/json` response - Accept header validation requires only `application/json` - Returns 406 when Accept header is missing - Accepts wildcard `*/*` in Accept header - Request-scoped notifications (progress, log) during tool execution are silently dropped - Request-scoped notifications do not leak to GET SSE even when connected - Session-scoped standalone notifications are delivered via GET SSE - Broadcast notifications are delivered via GET SSE - `send_request` raises for `sampling/createMessage`, `roots/list`, `elicitation/create` - Combined with `stateless: true`: POST returns JSON without a session ID, GET returns 405 ## Breaking Changes None. JSON response mode is opt-in via `enable_json_response: true`. The default behavior (SSE responses) is unchanged.
This commit is contained in:
parent
0770e27c67
commit
017b0bb4c9
1 changed files with 448 additions and 4 deletions
16
README.md
16
README.md
|
|
@ -1425,6 +1425,22 @@ Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new`
|
|||
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
|
||||
```
|
||||
|
||||
You can enable JSON response mode, where the server returns `application/json` instead of `text/event-stream`.
|
||||
Set `enable_json_response: true` in `MCP::Server::Transports::StreamableHTTPTransport.new`:
|
||||
|
||||
```ruby
|
||||
# JSON response mode
|
||||
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, enable_json_response: true)
|
||||
```
|
||||
|
||||
In JSON response mode, the POST response is a single JSON object, so server-to-client messages
|
||||
that need to arrive during request processing are not supported:
|
||||
request-scoped notifications (`progress`, `log`) are silently dropped, and all server-to-client requests
|
||||
(`sampling/createMessage`, `roots/list`, `elicitation/create`) raise an error.
|
||||
Session-scoped standalone notifications (`resources/updated`, `elicitation/complete`) and
|
||||
broadcast notifications (`tools/list_changed`, etc.) still flow to clients connected to the GET SSE stream.
|
||||
This mode is suitable for simple tool servers that do not need server-initiated requests.
|
||||
|
||||
By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
|
||||
When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue