Skip to content

Align the 2026-07-28 wire with spec PR #3002 and SEP-2575 HTTP statuses - #1752

Merged
tarekgh merged 7 commits into
modelcontextprotocol:mainfrom
pcarleton:paulc/sep-2575-pr3002-alignment
Jul 27, 2026
Merged

Align the 2026-07-28 wire with spec PR #3002 and SEP-2575 HTTP statuses#1752
tarekgh merged 7 commits into
modelcontextprotocol:mainfrom
pcarleton:paulc/sep-2575-pr3002-alignment

Conversation

@pcarleton

@pcarleton pcarleton commented Jul 27, 2026

Copy link
Copy Markdown
Member

⚠️ I have written very little C#, please use this PR as an input if it's helpful or fully discard. This came up as I was working on conformance testing. We had a late-breaking spec change to add serverInfo in response metadata as a SHOULD, 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).

  • Server: discover result carries serverInfo in _meta/io.modelcontextprotocol/serverInfo (body field no longer emitted, matching go-sdk); requests without _meta clientInfo are served; missing _meta/protocolVersion or /clientCapabilities rejects 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's supported list agrees with what discover advertises.
  • Post-dispatch status mapping (-32601 to 404, -32021/-32022/-32020 to 400) uses a new HandlePostRequestAsync overload 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_WhenNoEventStreamStore stays 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).
  • Notifications are exempt from the missing-_meta rejection: the SDK client does not decorate notifications with _meta, so rejecting them silently dropped notifications/cancelled and left server-side work running.
  • Client: reads server identity from _meta with a body fallback for pre-#3002 servers; connect no longer throws on the new shape.
  • Conformance fixtures: adds the three SEP-2575 diagnostic tools to ConformanceServer and the sep-2322-client-request-state / json-schema-ref-no-deref scenarios to ConformanceClient.

Breaking change note

Pre-release clients built from earlier main (DiscoverResult with required ServerInfo) 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)

Suite Before After
server, all 90/110 107/111
server-stateless scenario 11/27 28/28
client, all 311/321 331/332

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)
  • Conformance runs above, plus raw curl probes of the stateless endpoint (404/-32601 with id echo for removed methods, 400/-32602 for missing _meta fields, 202 for _meta-less notifications, serverInfo in _meta only)

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 (its DiscoverResultCloner will want to carry _meta serverInfo once both land).

pcarleton and others added 2 commits July 27, 2026 15:28
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>
@PranavSenthilnathan

Copy link
Copy Markdown
Contributor

I suggest bumping the conformance test version in package.json (and lock file) to 0.2.0-alpha.10 with this PR as well.

tarekgh added 3 commits July 27, 2026 13:13
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.
@tarekgh

tarekgh commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

I suggest bumping the conformance test version in package.json (and lock file) to 0.2.0-alpha.10 with this PR as well.

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.

Comment thread src/ModelContextProtocol.Core/Protocol/DiscoverResult.cs Outdated
tarekgh added 2 commits July 27, 2026 15:13
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.
@tarekgh
tarekgh merged commit 26f60ab into modelcontextprotocol:main Jul 27, 2026
10 checks passed
@jeffhandley jeffhandley added the breaking-change This issue or PR introduces a breaking change label Jul 27, 2026
@jeffhandley jeffhandley mentioned this pull request Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change This issue or PR introduces a breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants