Skip to content

Conversation

@findleyr
Copy link
Contributor

@findleyr findleyr commented Aug 7, 2025

Add support for JSON responses in the streamable HTTP server, and simplify the streamable client. See commit messages for details.

Fixes #211

@findleyr findleyr force-pushed the streamable branch 2 times, most recently from cda2f45 to c771949 Compare August 7, 2025 02:48
@findleyr findleyr marked this pull request as ready for review August 7, 2025 02:49
@findleyr findleyr requested review from jba and samthanawalla August 7, 2025 02:49
// Start the persistent SSE listener right away.
// Section 2.2: The client MAY issue an HTTP GET to the MCP endpoint.
// This can be used to open an SSE stream, allowing the server to
// communicate to the client, without the client first sending data via HTTP POST.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep this comment block with the initial handleSSE call

jba
jba previously approved these changes Aug 7, 2025
@findleyr
Copy link
Contributor Author

findleyr commented Aug 8, 2025

I believe I've figured out how to simplify this significantly. However, it's too hard to review. I'm going to break this up into smaller, logical PRs.

@findleyr findleyr marked this pull request as draft August 8, 2025 13:44
@findleyr findleyr force-pushed the streamable branch 3 times, most recently from f5d00b7 to ccc4321 Compare August 8, 2025 15:41
@findleyr findleyr marked this pull request as ready for review August 8, 2025 15:44
jba
jba previously approved these changes Aug 8, 2025
Copy link
Contributor

@jba jba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for splitting it up!

This CL introduces a fundamental change to the jsonrpc2 library:
connection writes, which were previously serialized by the jsonrpc2
library itself, are now allowed to be concurrent.

The change in semantics of the jsonrpc2 library should hopefully be easy
to review, since moving the synchronization to the Writer implementation
is equivalent to the previous logic. However, this change is critical
for the streamable client transport, because it allows for concurrent
http requests to the server.

Consider that a write is a POST to the server, and we don't know that
write succeeded until we get the response header. Previously, we had the
following problem: if the client POSTs a request, and the server blocks
its response on a request made through the hanging GET, the client was
unable to respond because the initial POST is still blocked. We could
update our streamable server transport to force a flush of the response
headers, but we can't guarantee that other servers behave the same way.
Fundamentally, writes in the spec are asynchronous, and we need to
support that.
Make several cleanups of the streamable client transport, encountered
during work on JSON support for the streamable server:

- The 'Close' condition is differentiated from asynchronous failures. A
  failure should unblock Read with an error, at which point the JSON-RPC
  connection will be broken and closed.
- Fields are reordered in streamableClientConn to make guards more
  apparent.
- The handling of sessionID is simplified: we simply set the session ID
  whenever we receive response headers. No need to have special handling
  for the first request, as the serializeation of session initialization
  is implemented in Client.Connect.
- Since the above bullet makes Write a trivial wrapper around
  postMessage, the two methods are merged.
- A bug is fixed where JSON responses were handled synchronously in
  Write. This lead to deadlock when a hanging client->server request is
  waiting on a server->client request. Now JSON is handled symmetrically
  to SSE: the Write returns once response headers are received.
  asynchronous to Write.
- The httpConnection interface is renamed to clientConnection, and
  receive the entire InitializeResult.
- streamableClientConn receivers are renamed to be consistently 'c'.
Add a new (currently unexported) jsonResponse option to
StreamableServerTransportOptions, and use it to control the response
content type, serving application/json if set.

Additionally:
- A transportOptions field is added to the StreamableHTTPOptions.
- A messages iterator is added to encapsulate the iteration of stream
  messages, since the handling of JSON and SSE responses are otherwise
  very different.
- The serving flow is refactored to avoid returning (statusCode,
  message), primarily because this seemed liable to lead to redundant
  calls to WriteHeader, because only local logic knows whether or not
  any data has been written to the response.
- The serving flow is refactored to delegate to responseJSON and
  responseSSE, according to the currently unexported jsonResponse
  option.
- A bug is fixed where all GET streams were considered persistent: the
  terminal condition req.Method == http.MethodPost && nOutstanding == 0
  was not right: GET requests may implement stream resumption.

Updates modelcontextprotocol#211
@findleyr findleyr merged commit 0375535 into modelcontextprotocol:main Aug 8, 2025
5 checks passed
@samthanawalla
Copy link
Contributor

LGTM 👍

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.

Proposal: add StreamableServerTransport.JSONResponse to support application/json format messages in StreamableHTTP Transport

3 participants