SEP-2322: Multi Round-Trip Requests #2322
SEP-2322: Multi Round-Trip Requests #2322CaitieM20 wants to merge 56 commits intomodelcontextprotocol:mainfrom
Conversation
…emove garbage file
…uestParams — now available on any
client-initiated request, not just tool calls
2. Added typed InputResponse = ElicitResult | CreateMessageResult — InputResponses values are now properly typed
instead of { result: { [key: string]: unknown } }
3. Updated examples — removed the extra result wrapper from TaskInputResponseRequest and
TaskInputResponseRequestParams examples to match the new typed schema
All checks pass: ✅ TypeScript compiles, ✅ JSON schema up to date, ✅ 140/140 examples valid.
Caitie Note: Need to take a look at task-input-response-params.json looks wrong on cursory inspection.
…ngInputRequest → SamplingCreateRequest.
…itRequets these should only be sent as part of IncompleteResponse Objects now
…a JSONRPCIncompleteResultResponse and add examples
…pper in the SEP because InputRequests maps keys directly to the requests so the InputResponses now follows that pattern and there is no benefit to the added wrapper. The only argument for the wrapper would be if we wanted to carry error's alongside results but we do not in these cases. If there is an error the client would retry until the necessary information was retrieved and then send back to the server.
…ut content ordering changes
MRTR schema changes
This can be handled in coupled way with what SEP-1442 proposes and can be linked to the backwards compatibility of that SEP. If a server does not receive the protocol version in a |
Add ResultType to handle polymporphic resposes.
|
Here are the vote after discussion in 3/4 Core Maintainer's meeting:
Next steps are to have a reference implementation in one of the SDKs (TS/Python) and collect feedback from SDK maintainers on it. |
|
Responding to the Python / TypeScript SDK questions @pcarleton @maxisbey @pja-ant @felixweinberger also adding in @halter73 from C#
Agree here. Perhaps we just launch with a Skill that can help do the upgrade.
We should make sure the Client SDKs have an option to NOT support the old version. For example we have several Cloud Hosted Clients that can't support elicitations & sampling today because of the requirement for SSE streams. It would be great if we could make this a config parameter to say I only support MRTR elicitation/sampling/listRoots, etc...
How much of this can be handled via version negotiation vs capabilities negotiation. If an MCP server is on the June 2026 release and the client doesn't support it then I think they are fundamentally incompatible? Do we forsee any issues with the proposal above which is essentially saying I support a mixed version? One potential way to solve this is to add new capabilities elicitation-mrtr, sampling-mrtr, etc... This would allow clients to specify what versions of these features they support. They could support both, one, or none. |
I think the problem isn't version or capability negotiation, it's that the new and old forms of elicitation/sampling are fundamentally incompatible because of the differing deployment model between MRTR and session-based requests. And that makes it very hard for SDKs to build in a way that e.g. we support both 2025-11 and 2026-06 elicitation and sampling at the same time on the same server. Let's say we have a server that advertises it supports both the 2025-11 and 2026-06 specs. It has tools that use elicitation. In theory, when a 2025-11 client connects, it should use SSE. If a 2026-06 client connects, it should use MRTR. Now, there's a scenario that's fundamentally unresolvable in the matrix below (lower left quadrant) if the server is deployed in a way to take advantage of statelessness.
The key problem is that no form of capability negotiation can solve the bottom left quadrant. So one resolution could be for servers that support the 2025-11 and 2026-06 spec at the same time, they should build with MRTR and not use elicitation / sampling if the negotiated version is 2025-11. I believe one solution to this would be an addition to the spec in the form:
Which implies that for elicitation and sampling the server developer has to make an explicit decision to either (A) support both SSE and MRTR (and implement the infra for it somehow) or (B) not use elicitation or sampling with pre- |
Following up from the Transports WG call, here's a quick prototype of what backwards compatibility could look like using the typescript sdk as an example: modelcontextprotocol/typescript-sdk#1701 On the server side there are several options for how this could be designed with different degrees of "magic" - on the client side, if we just keep around the "old" sse based code, I think we can pretty much hide it entirely such that a client using a 2026-06 based SDK should still be able to use older servers without having to change any code - the SDK can manage which approach to use with the server based on version negotiation. |
|
Hi all, I played a bit with this proposal to see how it could be incorporated into the Go SDK. Below are my thoughts. I agree with the statements that wire compatibility is handled by protocol version negotiation (though it will complicate the SDK, details later on) and that client-side story should be largely transparent to developers. On the server side, I see two main compatibility scenarios. 1. Previously implemented server updating the SDK version to be
|
|
EDIT: I deleted my previous response where I was discussing sending requests the old way over the SSE stream being similar more similar to stdio which didn't fully account for the fact stdio is changing with this SEP too. I'm going to spend more time looking at making MRTR work with the existing As long as we can support that, which I think we can, removing the old way of doing things from the protocol is less painful (at least for users of our SDKs). If you use the existing We'd still need to provide a lower-level API that gives you more control of the MRTR details on the server in particular where we don't want to force developers into session affinity which is always going to be required to support This is the bottom left quadrant @felixweinberger mentions, and I agree that no form of capability negotiation can solve it, so language to the effect "A server MUST NOT return |
@CaitieM20 I agree this makes sense. I think the most sensible way to do this is for the clients to expose some sort of "I don't want SSE streams" (in)capability since that's the root concern. And the most obvious way to do that would be to relax this requirement in the next iteration of the Streamable HTTP spec: modelcontextprotocol/docs/specification/draft/basic/transports.mdx Lines 92 to 93 in fe36698 It could be updated to say something like the following:
The SDK maintainers will then certainly be asked to expose control over this via some client API, and would be forced to gate this feature to when it connects to a server using |
| // retries the original request. | ||
| // Note: The client must treat this as an opaque blob; it must not | ||
| // interpret it in any way. | ||
| requestState?: string; |
There was a problem hiding this comment.
If this will be serialized JSON, any thoughts about maximum length? It could get large.
There was a problem hiding this comment.
In general, we've been avoiding putting maximum limits in the protocol. I think it's better to leave it up to the implementations to set limits similar to HTTP headers.
We could consider some specific status/error mechanism for indicating the request state is too large, similar to 431 Request Header Fields Too Large, but I'm not sure that's necessary either. Presumably it'd be the client that'd want to enforce limits (the server is free not to use this after all), and clients don't have an equivalent to 431 to inform the server its HTTP response headers are too long.
Presumably servers are going to try to keep requestState as short as they can for maximum compatibility. That's certainly what we do in ASP.NET Core for response headers and why we use things like the ChunkingCookieManager. I'm not that I'm saying you could chunk requestState, but I'm just pointing out that HTTP servers already have to deal with unspecified client limits for response headers.
If clients/SDK want to enforce some sensible default limit, they can provide a clear error to the developer/user when it's exceeded, and some configuration to change the limit similar to the .NET's HttpClientHandler.MaxResponseHeadersLength property.
I definitely don't think we should be baking any hard limits into the protocol.
| // present, since the presence of these fields allow the client to | ||
| // determine that the result is incomplete. | ||
| export interface IncompleteResult extends Result { | ||
| status: "incomplete"; |
There was a problem hiding this comment.
| status: "incomplete"; | |
| result_type: "incomplete"; |
| // At least one of the the inputRequests and requestState fields must be | ||
| // present, since the presence of these fields allow the client to | ||
| // determine that the result is incomplete. |
There was a problem hiding this comment.
| // At least one of the the inputRequests and requestState fields must be | |
| // present, since the presence of these fields allow the client to | |
| // determine that the result is incomplete. |
| values as untrusted input and validate them the same way they would | ||
| validate any client-supplied data. | ||
|
|
||
| 2. **Client Behavior:** |
There was a problem hiding this comment.
Sorry if this answered elsewhere... The server will know that the client supports MRTR by the Protocol version?
There was a problem hiding this comment.
I think that ought to be enough, considering this is the only way to do elicitation, sampling or roots calls using the upcoming protocol version assuming this SEP is accepted. That's how I currently have it working for the C# SDK in modelcontextprotocol/csharp-sdk@main...halter73/mrtr. Note that this is an early prototype which is why there is no PR yet.
There was a problem hiding this comment.
I created a draft PR for the C# changes so people can comment on individual changes at modelcontextprotocol/csharp-sdk#1458
Here's a key bit from the PR description for how I decided to handle backward compatability.
1. Full Backwards Compatibility via Protocol Negotiation
This was the most debated topic in the spec PR (felixweinberger, maciej-kisiel, CaitieM20). The C# SDK proves all four combinations work seamlessly:
Server Client Behavior Experimental ✅ Experimental ✅ MRTR — incomplete results with retry cycle Experimental ✅ Stable ✅ Fallback — ElicitAsync/SampleAsyncautomatically send legacy JSON-RPC requestsStable ✅ Experimental ✅ Client accepts stable protocol; MRTR retry loop is a no-op Stable ✅ Stable ✅ Standard behavior — no MRTR, no changes Key insight: The existing
await server.ElicitAsync(...)API doesn't change at all. When the connected client supports MRTR, the SDK returns anIncompleteResultwithinputRequestsinstead of sending aelicitation/createJSON-RPC request. When the client doesn't support MRTR, it sends the legacy request. Tool authors don't need to know or care which path is taken.The determination is made via protocol version negotiation during
initialize:// Server-side check (McpServerImpl.cs) internal bool ClientSupportsMrtr() => _negotiatedProtocolVersion is not null && _negotiatedProtocolVersion == ServerOptions.ExperimentalProtocolVersion;
It's pretty seamless for users of the old await API which I think will and should continue to remain popular particularly for people using an MCP server locally either via the stdio or HTTP transport (which makes debugging way easier), so they don't need to worry about statefulness being in anyway difficult.
It also provides a new non-await based API that allows you direct to control the IncompleteResult and InputRequests/InputResponses that works in stateless mode which is of course the whole motivation of this feature. I think it's important to support that without breaking elicitation and sampling code that already works though.
It's worth noting that the protocol-level-session was very handy in providing a reasonable lifetime for tracking pending MRTR flows when using the await machinery that's really convenient when you can rely on it, but I'll get into this more in a separate comment.
Why Removing Protocol-Level Sessions Would Block MRTR Adoption
The Backwards Compatibility PromiseThe strongest argument for MRTR is that it doesn't have to be a breaking change for server developers. In the C# SDK's implementation (PR #1458), existing tool handlers that call This backwards compatibility depends on sessions. How the High-Level API Depends on SessionsWhen a tool handler calls Without the session header, the retry has no way to find the right Per-Session MRTR GovernanceThe C# SDK's filter system lets developers intercept JSON-RPC messages at the transport boundary. Because the outgoing filter sees Why per-session limits and not just per-user?Per-user limits are usually the primary defense, but per-session limits serve distinct needs:
Per-user limits require authentication infrastructure. Per-session limits work out of the box because session identity is a protocol-level concept. The two are complementary. The Low-Level API Doesn't Replace ThisPR #1458 also provides a low-level API where tool handlers throw That's a reasonable API for advanced scenarios, but it's not a migration path for existing servers that use With sessions, the migration path is:
Without sessions, it becomes: rewrite all handlers to use ConclusionThe session removal proposal raises real concerns worth addressing. But those concerns can be addressed incrementally — by making session lifetime more explicit, adding cache-control headers, or allowing multiple state handles per session. Removing sessions entirely would make it very difficult for SDKs to deliver the seamless MRTR migration experience that server developers need. The C# SDK implementation in PR #1458 shows that with sessions, existing If both MRTR and session removal are on the table, I'd recommend advancing MRTR first and keeping sessions as the foundation for the high-level API. If we cannot keep sessions, I believe we should not approve MRTR in its current form — not because the protocol design is wrong, but because the SDK-level developer experience would suffer enough to limit adoption. |
| For example, a server might send the following input requests: | ||
|
|
||
| ```json5 | ||
| "inputRequests": { |
There was a problem hiding this comment.
Is there an implied ordering here? What if the inputRequests contains multiple elicitation/create messages? How is the client to handle that?
| [key: string]: InputRequest; | ||
| } | ||
|
|
||
| export type InputResponse = |
There was a problem hiding this comment.
InputResponse needs to include an optional error JsonRpcResponse, or at minimum the error stanza. I'm working on some initial designs for MRTR and we need a way to inform the tool author that an InputRequest has failed.
There was a problem hiding this comment.
Or, is the idea that the tool doesn't care? I guess one could make that argument.
There was a problem hiding this comment.
I wouldn't mind putting the error information back for better SDK-level compatibility. I think this is something @mikekistler pointed out too.
[snip] @halter73 makes a good point here. For our server, we're going to have tool developers adopt MRTR and we will emulate it inside the server (with a blocking thread). However, existing tools must be refactored. This could be a large burden if there are a lot of tools and it they are complex. |
This is a draft SEP for Multi Round-Trip Requests (MRTR) from the Transports Working Group. It is one of the changes discussed and planned at the December 2025 Core Maintainer Meetup. Exploring the Future of MCP Transports Blog
This SEP Is still in draft, Schema changes are proposed but not locked, documentation updates, conformance tests and additional work still to come, but its ready to move out of Transports Working Group and open up to broader feedback.
Authors: Mark D. Roth (@markdroth), Caitie McCaffrey (@CaitieM20), Gabriel Zimmerman (@gjz22)
Motivation and Context
See SEP for details.
How Has This Been Tested?
Not tested yet, in progress work
Breaking Changes
Yes, see SEP for details
Types of changes
Checklist
Additional context