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
This was referenced 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