Skip to content

Support JSON response mode for StreamableHTTPTransport#328

Merged
koic merged 1 commit intomodelcontextprotocol:mainfrom
koic:support_json_response_mode
Apr 23, 2026
Merged

Support JSON response mode for StreamableHTTPTransport#328
koic merged 1 commit intomodelcontextprotocol:mainfrom
koic:support_json_response_mode

Conversation

@koic
Copy link
Copy Markdown
Member

@koic koic commented Apr 21, 2026

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.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## 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.
@koic koic force-pushed the support_json_response_mode branch from 26a87ed to 017b0bb Compare April 21, 2026 23:30
@koic koic merged commit a89047a into modelcontextprotocol:main Apr 23, 2026
11 checks passed
@koic koic deleted the support_json_response_mode branch April 23, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants