-
Notifications
You must be signed in to change notification settings - Fork 178
Add Server Streamable Http Transport #235
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
base: main
Are you sure you want to change the base?
Conversation
|
Hi @e5l @devcrocod @SeanChinJunKai , would you mind taking a look? |
|
Hi @devcrocod , would you mind taking a look when available? |
6820649 to
465f3dd
Compare
kpavlov
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.
Thank you @zshea for the PR
Would it be possible to add integration tests to make sure it actually works?
Also, id is mandatory for JSONRPCRequest, JSONRPCResponse, so let's keep it non-nullable
kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt
Outdated
Show resolved
Hide resolved
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt
Show resolved
Hide resolved
dvilker
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.
I tried your PR in practice in Stateless mode and brought back what I found.
...commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt
Outdated
Show resolved
Hide resolved
...commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt
Show resolved
Hide resolved
| block: RoutingContext.() -> Server, | ||
| ) { | ||
| routing { | ||
| post("/mcp") { |
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.
get requests also need to be processed.
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.
I didn't implement because it's not needed for json response. Once we add SSE back, we can do it then.
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.
I didn't implement because it's not needed for json response. Once we add SSE back, we can do it then.
https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http
The server MUST either return Content-Type: text/event-stream in response to this HTTP GET, or else return HTTP 405 Method Not Allowed, indicating that the server does not offer an SSE stream at this endpoint
Without processing (which responds with code 405), it seems the inspector was spamming errors.
I replaced post(... with route(".... Everything else has already been implemented by you.
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.
I'm drafting a PR to return 405 for the stateless extension on GET requests, as per spec
The server MUST either return Content-Type: text/event-stream in response to this HTTP GET, or else return HTTP 405 Method Not Allowed, indicating that the server does not offer an SSE stream at this endpoint.
|
Hi @zshea Thanks for this PR. In relation to:
Was an issue created on ktor for this? |
|
One thing that's not clear to me
Looking at the spec the client MUST be able to accept a json response. Is this a bug in the client? From the spec:
|
|
I played around with this a bit.
|
465f3dd to
b01e472
Compare
I found an answer here: https://youtrack.jetbrains.com/issue/KTOR-8327/SSE-Document-how-to-receive-SSE-requests-with-POST-and-other-methods#focus=Comments-27-12594500.0-0 The workaround significantly changes the architectureof how we implement transport: need to split header response and body response. |
TBH I've been struggling with adding tests here, would appreciate some help here, or I can dive more later. Sorry almost forgot this PR for a while... |
835e45e to
128f798
Compare
…to include `TransportSendOptions`. Added support for optional resumption tokens and progress callbacks in request handling. # Conflicts: # kotlin-sdk-server/api/kotlin-sdk-server.api
…r` usage. Updated references from `ErrorCode` to `RPCError.ErrorCode`. Adjusted imports for consistency with `types` package structure.
…to include `TransportSendOptions`. Added support for optional resumption tokens and progress callbacks in request handling.
…, simplify `session` handling, and replace `LATEST_PROTOCOL_VERSION` with `DEFAULT_NEGOTIATED_PROTOCOL_VERSION`. Adjusted request validation and related imports.
128f798 to
ecaeb03
Compare
… and request validation, and optimized DNS rebind protection logic. Added safeguards for headers and session cleanups.
Add support for `TransportSendOptions` in `Transport.send` method ## Motivation and Context This change extends the `Transport.send` method signature to support advanced transport features like resumption tokens and progress callbacks. This is needed to enable: - Long-running request resumption after reconnection - Request-response association tracking - Progress token handling in transport layer needed for #235 ## How Has This Been Tested? All transport tests passed ## Breaking Changes Yes, it’s necessary to update the custom Transport implementations. For users who rely on the transports provided by the sdk, there’s no breaking change ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed
|
Thanks @devcrocod for the work on resolving the merge conflicts. I'm creating integration tests for this, but I'm running into an issue where Ktor is sending 406 Not Acceptable on the Streamable Server Transport for the initialize call, despite an OK payload. There shouldn't be any issues serializing the JSONRPCResponse. This wasn't happening with OldSchema integration tests, but now it seems to be happening. I'll look into this more soon, but I'll leave some details here in case there's some obvious I'm missing. The attempted JSON RPC payload is as follows: However, this is what the Ktor server is sending back. Here's how i'm starting the test server TransportKind.STREAMABLE_HTTP_STATELESS -> {
serverEngine = embeddedServer(ServerCIO, host = host, port = port) {
install(CallLogging) {
level = Level.INFO
format { call ->
val status = call.response.status()
val httpMethod = call.request.httpMethod.value
val path = call.request.path()
"Status: $status, Method: $httpMethod, Path: $path"
}
}
routing {
mcpStatelessStreamableHttp { server }
}
}.start(wait = false)
} |
|
Hey @joewlambeth, I didn’t open a separate pull request and instead continued the work in the same branch While going through the review of this PR, I fixed a number of issues I discovered both in the streamable implementation itself and in our protocol. Next, I’m planning to polish the implementation, review the KtorServer logic, and write an integration test using the typescript client against our server to verify that the basic streamable transport flow works correctly. I think this should be enough to merge these changes. After that, we’ll add broader test coverage for the streamable server transport, as well as conformance tests, to make sure we fully align with the specification. Thanks a lot for the example you provided, it’s definitely helpful. We also downgraded the Ktor version recently, which could be another reason why your test stopped working. I’ll take a closer look at this when I work on the integration test with the typescipt client |
Closes #79 #183
Implement server StreamableHttpTransport (both stateful and stateless)
This is a workable solution, tested with Claude Code
Caveat:
enableJsonResponse = true. We can remove this limitation once the upstream has fixed this issue.StreamHttpTransportassumes the SSE stream while the server assumes the JSON response.Motivation and Context
It is painful to support
/sse+/messageendpoint on production. Hopefully we can close this issue with this PR.How Has This Been Tested?
It has been tested in my environment with Claude Code (1.0.89) MCP with both
mcpStreamableHttpandmcpStatelessStreamableHttpBreaking Changes
No, everything is backward compatible
Types of changes
Checklist
Additional context
It is workable with the following implementation:
But the response is always 200, even if we set the status explicitly. And the connection won't be closed with a 4xx or 5xx response. It breaks the MCP spec and can't be used directly. I think we need to create issues to upstream.