-
Notifications
You must be signed in to change notification settings - Fork 285
mcp: refactor streamable serving and support JSON responses #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
cda2f45 to
c771949
Compare
mcp/streamable.go
Outdated
| // 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. |
There was a problem hiding this comment.
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
|
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. |
f5d00b7 to
ccc4321
Compare
jba
left a comment
There was a problem hiding this 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
|
LGTM 👍 |
Add support for JSON responses in the streamable HTTP server, and simplify the streamable client. See commit messages for details.
Fixes #211