Skip to content

Fix request-scoped draft client capabilities#1685

Open
PranavSenthilnathan wants to merge 2 commits into
modelcontextprotocol:mainfrom
PranavSenthilnathan:pranavsenthilnathan/fix-stdio-capabilities-race
Open

Fix request-scoped draft client capabilities#1685
PranavSenthilnathan wants to merge 2 commits into
modelcontextprotocol:mainfrom
PranavSenthilnathan:pranavsenthilnathan/fix-stdio-capabilities-race

Conversation

@PranavSenthilnathan

Copy link
Copy Markdown
Contributor

Fixes #1670

Under the 2026-07-28 draft protocol (SEP-2575) there is no initialize handshake; each
request carries the client's capabilities/info per-request in _meta. The server was
persisting these into shared session state on McpServerImpl, so concurrent requests on a
single stateful session (stdio/stream/stateful HTTP) raced and observed each other's
capabilities. The spec requires a server MUST NOT infer client capabilities from previous
requests.

This moves resolution into the per-request DestinationBoundMcpServer:

  • ClientCapabilities: stateless -> null (keeps the "unsupported in stateless mode"
    sentinel); 2026-07-28+ -> this request's _meta value, or empty when absent (never
    inferred from another request); legacy -> session state from initialize.
  • ClientInfo: same shape (request-scoped for 2026-07-28+, session for legacy).
  • McpServerImpl.PrependMetaReadingFilter no longer writes per-request capabilities to
    shared state (the race source). It still syncs client info for endpoint-name logging,
    which handlers no longer read on draft sessions.

Tests: one fires two overlapping tool calls with different _meta capabilities and asserts
each sees its own; another asserts a tool observes the per-request client info.

PranavSenthilnathan and others added 2 commits June 30, 2026 18:17
Route client capability resolution through request-scoped DestinationBoundMcpServer state for 2026-07-28+ requests, stop persisting per-request capabilities into McpServerImpl global state, and add a concurrency regression test that verifies overlapping requests observe their own declared capabilities.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gate DestinationBoundMcpServer.ClientInfo on the 2026-07-28+ protocol like
ClientCapabilities so request handlers observe this request's declared client
info rather than falling back to shared session state, which under a stateful
transport could belong to a different concurrent request. Add a regression test
verifying a tool observes the per-request client info.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a concurrency race in draft (2026-07-28 / SEP-2575) sessions by ensuring client capabilities and client info are resolved per request from _meta, rather than being persisted into shared McpServerImpl session state.

Changes:

  • Moves draft-protocol ClientCapabilities / ClientInfo resolution into DestinationBoundMcpServer so concurrent requests don’t observe each other’s _meta.
  • Stops McpServerImpl.PrependMetaReadingFilter from writing per-request client capabilities into shared server state.
  • Adds regression tests for concurrent per-request capabilities and request-scoped client info observation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs Adds concurrency and request-scoped meta tests for draft protocol behavior.
src/ModelContextProtocol.Core/Server/McpServerImpl.cs Removes shared per-request capability state sync; threads request context into the destination-bound server wrapper.
src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs Implements request-scoped ClientCapabilities/ClientInfo resolution for 2026-07-28+ requests.

Comment on lines +8 to +13
internal sealed class DestinationBoundMcpServer(McpServerImpl server, ITransport? transport, JsonRpcRequest? jsonRpcRequest = null) : McpServer
#pragma warning restore MCPEXP002
{
private readonly bool _isJuly2026OrLaterRequest = IsJuly2026OrLaterProtocolRequest(jsonRpcRequest, server.NegotiatedProtocolVersion);
private readonly ClientCapabilities? _requestClientCapabilities = jsonRpcRequest?.Context?.ClientCapabilities;
private readonly Implementation? _requestClientInfo = jsonRpcRequest?.Context?.ClientInfo;

private readonly TaskCompletionSource<JsonNode?> _initializeMeta = new();

private const string ClientCapabilitiesMetaKey = "io.modelcontextprotocol/clientCapabilities";
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.

Draft protocol: concurrent stdio requests race on shared client-capabilities state

2 participants