Align the 2026-07-28 wire with spec PR #3002 and SEP-2575 HTTP statuses - #1752
Conversation
Spec PR modelcontextprotocol/modelcontextprotocol#3002 moved server identity out of the server/discover result body into the result's _meta/io.modelcontextprotocol/serverInfo and demoted the per-request clientInfo from required to SHOULD. The conformance suite enforces the new shape as of 0.2.0-alpha.10, where csharp-sdk failed 16 server checks and 8 client scenarios. Server: - Emit serverInfo in the discover result _meta and drop it from the body (the DiscoverResult property remains, optional, so clients can still read pre-#3002 servers). - Serve requests whose _meta omits clientInfo instead of rejecting. - Reject requests missing _meta/protocolVersion or /clientCapabilities with -32602 Invalid params at HTTP 400, echoing the request id. -32020 HeaderMismatch is reserved for values present on both sides that disagree. Notifications are exempt from the missing-_meta rejection: they are fire-and-forget and rejecting them silently drops signals like notifications/cancelled. - Return 404 with -32601 for the RPCs the per-request-metadata revisions removed (initialize, ping, logging/setLevel, resources/subscribe, resources/unsubscribe) and for unknown methods. The removed-method rejection lives in the HTTP handler because it is a property of the stateless HTTP surface; other transports keep serving these methods on the revisions that define them. - Map post-dispatch JSON-RPC errors onto HTTP statuses (-32601 to 404, -32021/-32022/-32020 to 400) via a new HandlePostRequestAsync overload that reports the first response message before any bytes are written. The eager header flush is deferred behind a bounded 250ms grace window so long-running tool calls still see response headers promptly. - Restrict the UnsupportedProtocolVersionError supported list on stateless servers to the per-request-metadata revisions so it agrees with what server/discover advertises. - Validate the MCP-Protocol-Version header after the body parse so its rejection can echo the request's JSON-RPC id. Client: - Read server identity from the discover result _meta, falling back to the body for pre-#3002 servers; connecting no longer throws when the body field is absent. Conformance fixtures: - Add the SEP-2575 diagnostic tools (test_missing_capability, test_streaming_elicitation, test_logging_tool) to ConformanceServer and the sep-2322-client-request-state and json-schema-ref-no-deref scenarios to ConformanceClient. Against @modelcontextprotocol/conformance 0.2.0-alpha.10 at --spec-version 2026-07-28 this takes the server suite from 90/110 to 107/111 (server-stateless 28/28; the remaining failures are the pre-existing json-schema-2020-12 and dns-rebinding-protection gaps) and the client suite from 311/321 to 331/332 (remaining: auth/authorization-server-migration).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
I suggest bumping the conformance test version in package.json (and lock file) to 0.2.0-alpha.10 with this PR as well. |
Under the 2026-07-28 protocol the server rejects resources/subscribe and resources/unsubscribe with MethodNotFound. Include a migration hint in the rejection pointing callers to subscriptions/listen with resourceSubscriptions, document the removal on the client subscribe/unsubscribe APIs, and add gating tests mirroring the ping gating coverage.
ValidateRequiredPerRequestMeta only null-checked _meta clientCapabilities, so a present-but-wrong-shape value passed the HTTP validation and later failed during deserialization as a generic -32603 internal error on a 200 response. Require it to be a JSON object so both missing and malformed values are rejected up front with -32602 and HTTP 400 as SEP-2575 requires.
When the deferred header flush fails after the response future has already been resolved, TrySetException is a no-op and the failure was swallowed silently. Log it as a warning in that case so the failure stays diagnosable, matching the existing event-stream disposal logging.
Thanks @PranavSenthilnathan. I validated 0.2.0-alpha.10 locally against the changes in this PR. I installed that conformance package into node_modules and ran the conformance suites, and everything passes: the AspNetCore conformance tests report 190 passed, 0 failed, 0 skipped, with every 2026-07-28 scenario actually running rather than being skipped (including the SEP-2575 request-metadata client scenario, http-header-validation, caching, and sep-2164-resource-not-found). So the bump is safe with these changes. I am holding off on updating package.json and package-lock.json in this PR for now. The build environment I am working in resolves npm through an internal mirror that has not synced 0.2.0-alpha.10 yet, so package-lock.json cannot be regenerated reliably there at the moment. Once the mirror picks up the new version, I will bump both package.json and the lock file and push the update. If you would prefer, I am happy to land the version bump as a separate follow-up. |
The 2026-07-28 revision carries serverInfo in the result _meta (io.modelcontextprotocol/serverInfo), not the discover body. Since server/discover and its serverInfo were both introduced in this revision and serverInfo moved to _meta within it, no released protocol version carried serverInfo in the body, so removing the body property is not a breaking change against the finalized spec. Remove the DiscoverResult.ServerInfo property, drop the client body fallback in GetServerInfoFromDiscover (read only from _meta), and update test mocks to emit serverInfo via _meta to match real servers.
serverInfoin response metadata as aSHOULD, so I pointed claude at getting the conformance tests to pass. This PR fixes a few failing conformance tests, but I don't have enough context on C# or this SDK to know whether getting them to pass this way is reasonable.Summary
Aligns the 2026-07-28 wire with spec PR modelcontextprotocol/modelcontextprotocol#3002 (serverInfo in result
_meta, clientInfo optional) and the SEP-2575 HTTP status requirements. This is the csharp-sdk counterpart of typescript-sdk#2513 / python-sdk#3143 / go-sdk#1097; csharp-sdk was the last Tier 1 SDK without it. Closes the corresponding gap under #1539 (the SEP-2575 line there was completed pre-#3002 and went stale when the spec PR changed the shape).serverInfoin_meta/io.modelcontextprotocol/serverInfo(body field no longer emitted, matching go-sdk); requests without_metaclientInfo are served; missing_meta/protocolVersionor/clientCapabilitiesrejects with -32602 at HTTP 400 with the request id echoed (-32020 stays reserved for genuine mismatches); removed RPCs (initialize, ping, logging/setLevel, resources/subscribe, resources/unsubscribe) and unknown methods return 404 with -32601 on the stateless HTTP surface; UnsupportedProtocolVersionError'ssupportedlist agrees with what discover advertises.HandlePostRequestAsyncoverload reporting the first response message before headers commit. The eager header flush is deferred behind a bounded 250 ms grace window, so long-running tools still get prompt headers (LongRunningToolCall_DoesNotTimeout_WhenNoEventStreamStorestays green). Once a handler streams a notification or outlives the grace window the status is committed; a later error rides it (noted in the XML docs)._metarejection: the SDK client does not decorate notifications with_meta, so rejecting them silently droppednotifications/cancelledand left server-side work running._metawith a body fallback for pre-#3002 servers; connect no longer throws on the new shape.sep-2322-client-request-state/json-schema-ref-no-derefscenarios to ConformanceClient.Breaking change note
Pre-release clients built from earlier main (DiscoverResult with
requiredServerInfo) fail to deserialize a discover result from this server, since the body field is gone. That matches the go-sdk approach and the draft schema; flagging it explicitly since both shapes share the 2026-07-28 version string.Conformance results (
@modelcontextprotocol/conformance@0.2.0-alpha.10,--spec-version 2026-07-28)Remaining failures are separate gaps: json-schema-2020-12 keyword preservation (SEP-2106), dns-rebinding-protection, and auth/authorization-server-migration (SEP-2352).
Testing
ModelContextProtocol.Tests: 2314 passed / 0 failed (net10.0)ModelContextProtocol.AspNetCore.Tests: 497 passed / 0 failed (net10.0)_metafields, 202 for_meta-less notifications, serverInfo in_metaonly)Heads up on overlap: #1709 touches the initialize path in
StreamableHttpHandler(complementary; this PR routes per-request-metadata initialize to 404 before the session, #1709 validates legit handshake initialize) and #1697 touches the client discover flow (itsDiscoverResultClonerwill want to carry_metaserverInfo once both land).