| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
…d (SEP-2575, SEP-2567) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…options (MCP9005) Default `HttpServerTransportOptions.Stateless` to true so new code on the 2026-07-28 draft revision (SEP-2567) is sessionless from the start. Mark the surface that only makes sense in the legacy stateful HTTP mode as obsolete behind the new MCP9005 diagnostic so callers see a deprecation hint but can still pin Stateless = false to keep using session-based behaviors during back-compat: * `HttpServerTransportOptions.EventStreamStore` (resumability) * `HttpServerTransportOptions.SessionMigrationHandler` (multi-node migration) * `HttpServerTransportOptions.PerSessionExecutionContext` * `HttpServerTransportOptions.IdleTimeout` * `HttpServerTransportOptions.MaxIdleSessionCount` Internal infrastructure that legitimately reads those options for the back-compat stateful path now suppresses MCP9005 at the use site. Test projects suppress it globally via NoWarn because the suite intentionally exercises both modes. Update tests/samples that previously relied on the implicit `Stateless = false` default to set it explicitly: * TestSseServer.Program — SSE always needs stateful state shared across GET/POST. * ConformanceServer.Program — resumability + OAuth conformance scenarios are stateful. * ResumabilityIntegrationTestsBase — resumability is a stateful concern. * SseIntegrationTests / MapMcpSseTests — SSE requires stateful. * OAuthTestBase — OAuth flow uses the GET /sse session-based endpoint. * MrtrProtocolTests / SessionMigrationTests / StreamableHttpServerConformanceTests — these tests intentionally drive the legacy stateful session machinery. * DraftHttpHandlerTests — tests draft rejection of GET/DELETE endpoints, which are only mapped when Stateless = false. Rework HTTP header conformance helpers (HttpHeaderConformanceTests + StreamableHttpServerConformanceTests) to stop asserting an mcp-session-id response header from draft/non-draft initialize, because the sessionless default means none is returned. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…to legacy protocol Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…of overwriting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Detect fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… #2855) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pec-version strings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- RawStreamConformanceTests.cs: wrap in #if !NET472 to avoid ReadLineAsync(CancellationToken) overload missing on .NET Framework. - HttpMcpServerBuilderExtensionsTests: IdleTrackingBackgroundService_StartsTimer_WhenStateful needs explicit Stateless=false after the default flipped to true in commit 8904958. - HttpHeaderConformanceTests: two tests used the old DRAFT-2026-v1 wire-version string which the server now rejects; updated to 2026-07-28. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The server validated that Mcp-Param-* header values are conformantly encoded (printable ASCII, or a "=?base64?...?=" wrapper for non-ASCII) but applied no such validation to the standard Mcp-Name header. Raw non-ASCII Mcp-Name values were passed through and compared byte-for-byte against the body name. Mirror the existing Mcp-Param-* validation for Mcp-Name: reject values containing characters outside the valid HTTP header value range, then decode the "=?base64?...?=" wrapper before comparing to the body value. This makes the server reject mis-encoded non-ASCII names and correctly accept compliant base64-wrapped non-ASCII tool/resource/prompt names. Fixes HttpHeaderConformanceTests.Server_RejectsInvalidUtf8EncodedHeaderValue, which previously passed only incidentally on the stateful draft path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The draft 2026-07-28 protocol removes stream resumability entirely (a dropped connection is treated as cancellation), so the SSE event stream store surface is legacy-only. Mark the resumability interfaces, options, and wire-up [Obsolete] under the existing MCP9005 (LegacyStatefulHttp) diagnostic, matching the already-obsoleted stateful HTTP options: - ISseEventStreamReader / ISseEventStreamWriter / ISseEventStreamStore - SseEventStreamMode / SseEventStreamOptions - StreamableHttpServerTransport.EventStreamStore - DistributedCacheEventStreamStoreOptions - WithDistributedCacheEventStreamStore Internal SDK usage of these now-obsolete types is suppressed with targeted MCP9005 pragmas (and project-level NoWarn where source generators emit code over the obsolete types). External consumers still receive the obsolete warning. Behavior is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Under the draft revision (SEP-2575 + SEP-2567) the HTTP request lifetime is the request lifetime: there are no sessions, so a dropped connection is equivalent to cancelling the in-flight request. Verify that aborting the HTTP request flows cancellation into a running tool handler's CancellationToken, covering both draft sessionless mode and legacy stateless mode (both are 1:1 request-to-handler). The tests drive raw HTTP via the in-memory Kestrel transport: a tool blocks on its injected CancellationToken, the client aborts the request mid-flight, and the tests assert the server observes RequestAborted and the tool's token fires. No production change was required; the existing session-disposal path already propagates the abort. These pin that behavior going forward. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…scenarios Adds the two SEP-2322 ConformanceServer tools that RunMrtrConformanceTest previously skipped as "not yet implemented": - test_input_required_result_tampered_state: R1 issues an HMAC-signed requestState; R2 with a tampered requestState surfaces a -32602 JSON-RPC error (McpProtocolException propagates as a protocol error, not an isError CallToolResult). - test_input_required_result_capabilities: emits inputRequests only for the capabilities the client declared on the per-request _meta clientCapabilities envelope (read via JsonRpcMessageContext.ClientCapabilities). Removes the per-row Skip from both [InlineData] rows so they run under the same HasMrtrScenarios() gate as the other MRTR scenarios. Verified live: 14/14 RunMrtrConformanceTest scenarios pass against the local compat/conformance-draft build (which emits the 2026-07-28 wire string). Adds in-process wire-level regression coverage in MrtrProtocolTests (TamperedRequestState_ReturnsJsonRpcError and CapabilityCheck_OnlyEmitsInputRequestsForDeclaredCapabilities) so both behaviors stay verified in CI even while the published conformance package's draft wire string lags this SDK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…atch When a draft request's MCP-Protocol-Version header disagrees with the per-request _meta io.modelcontextprotocol/protocolVersion value (SEP-2575), the server already rejected the request in PopulateContextFromMeta, but it used -32602 InvalidParams. A conformant draft client's server/discover probe treats any non-modern JSON-RPC error (including InvalidParams) as a legacy-server signal and falls back to the initialize handshake. That means a modern draft server that detected a genuine header/body mismatch would be misread as legacy. Emit -32001 HeaderMismatch instead -- the same code already used for the Mcp-Method/Mcp-Name header-vs-body checks and the exact code the client's probe recognizes as a modern-server signal to surface as-is (see McpClientImpl's catch (McpProtocolException ex) when (ex.ErrorCode == McpErrorCode.HeaderMismatch)). Adds a RawHttpConformanceTests regression asserting a header/_meta protocol-version mismatch yields -32001. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Flip McpClientImpl.ConnectAsync so a null ProtocolVersion (the default) prefers the draft revision (SEP-2575 + SEP-2567): the client probes with server/discover and transparently falls back to the legacy initialize handshake when the server doesn't support draft. The legacy branch now runs only when the caller explicitly pins a non-draft version, making draft opt-out rather than opt-in. Merge (rather than overwrite) the session-level client capabilities into each request's _meta envelope so per-request opt-ins already written by higher layers (e.g. the tasks-extension capability from GetMetaWithTaskCapability) survive now that draft _meta injection is the default path. Refresh the XML docs on McpClientOptions.ProtocolVersion / MinProtocolVersion, McpSession.DraftProtocolVersion, and McpSessionHandler.DraftProtocolVersion to describe the new default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Looks good! 👍
Copilot found a few things worth mentioning. I'll leave it to you to decide if any of these require changes.
Sorry, something went wrong.
Note that I have the same diagnostic ID in my PR too #1651. If we want to have a different Ids, either you change it or I can change it in my PR. Let me know what work better for you. |
Sorry, something went wrong.
On defaulting HttpServerTransportOptions.Stateless = trueFlagging the new Stateless = true default as the highest-impact change for existing AspNetCore server hosts: it silently disables server-initiated requests (sampling, elicitation, roots) and the SSE GET endpoint for any host that doesn't explicitly set Stateless = false. I want to suggest an alternative that removes the breaking change while keeping the draft/stateless direction for new code. The statefulness decision is already per-connectionIn StreamableHttpHandler.GetOrCreateSessionAsync, each new connection already branches on the peer:
So the server can already tell a draft peer from a legacy peer at session-creation time (the draft header is absent on a legacy initialize). The static = true default only changes the legacy branch, which is exactly the branch that breaks existing consumers. Suggestion: an auto-default keyed to the negotiated revisionMake Stateless a bool? defaulting to null meaning "auto":
This reuses the dispatch hook that already exists, makes the breaking change disappear for legacy server hosts, and still defaults new draft clients to the sessionless model. The cost is that bool -> bool? is itself a (smaller, source-compatible) API change, and "auto" is slightly less obvious to reason about than a fixed bool. Alternative: default from the server's own capabilitiesDefault stateless only when the server has nothing that needs a back-channel (no sampling/elicitation/roots handlers, no IdleTimeout/EventStreamStore), and default stateful otherwise. This is closer to "does this server need server-initiated requests," but it can still surprise a host that relies on session-scoped state (per-session DI, idle eviction) without using bidirectional features. Tradeoff summary
My preference is the bool? auto-default. |
Sorry, something went wrong.
There was a problem hiding this comment.
I have added a few comments will be good if you consider them. LGTM otherwise.
Sorry, something went wrong.
# Conflicts: # docs/list-of-diagnostics.md # src/Common/Obsoletions.cs
Make the 2026-07-28 draft revision (SEP-2575 sessionless + SEP-2567 no initialize / no Mcp-Session-Id) the default wire protocol negotiated by the client, while keeping full back-compat with the 2025-11-25 legacy era: - McpClientOptions gains MinProtocolVersion (refuse to fall back below a floor) and a documented, configurable draft-probe timeout for the dual-era connect handshake. - The client probes the draft revision first and transparently downgrades to the legacy initialize handshake when the server only speaks an older revision, caching the negotiated era per transport. - Lock the negotiated protocol version once per session: it may only move from unset to a concrete revision and never change afterward. - Require a non-null JSON-RPC request id and stop treating a null id as a notification, so a malformed request gets a proper error response instead of being silently dropped. - Gate the SEP-2663 Tasks client APIs to the draft revision. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Over HTTP the draft revision is unconditionally sessionless, so the server now enforces that the draft protocol and an Mcp-Session-Id can never coexist: - Reject any request that carries the draft MCP-Protocol-Version together with an Mcp-Session-Id (POST/GET/DELETE). - When Stateless = false, refuse a sessionless draft request with -32004 UnsupportedProtocolVersion so a dual-era client downgrades to the legacy initialize handshake and obtains the session the author opted into. Stateless = true (the default) still serves sessionless draft natively. - Gate the SEP-2663 Tasks extension to the draft revision on the server: tasks/* handlers reject non-draft requests with MethodNotFound, and task augmentation is only applied for draft clients that opt in. This lets the per-request capability sync overwrite rather than merge a partial legacy envelope. - Mark the stateful-only HttpServerTransportOptions knobs obsolete (MCP9006) now that the default is stateless, and remove the dead fan-out / forceStateless plumbing left over from the stateful draft path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- New: DraftStatefulFallbackTests (e2e draft-first client downgrades to a legacy session against a Stateless = false server), TaskDraftGatingTests (Tasks APIs are draft-only on both client and server), NegotiatedProtocolVersionTests (request-id strictness and once-only version locking), SubscriptionsListenTests, RequestIdTests, and JsonRpcMessageConverterTests. - Rework DraftHttpHandlerTests, MrtrProtocolTests, StreamableHttpServerConformanceTests, and MapMcp* to assert the strict draft-vs-session behavior and to move genuinely stateful MRTR coverage onto stdio / in-memory transports now that Streamable HTTP is always stateless for the draft revision. - Add a net472 build-compat guard in NegotiatedProtocolVersionTests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update the stateless concept doc for the strict server-side behavior (sessionless draft refused on Stateless = false; draft + Mcp-Session-Id always rejected) and renumber the stateful-knob diagnostic to MCP9006 (MCP9005 is now SEP-2577's Roots/Sampling/Logging deprecation on main). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Post-review cleanup pass over the draft-protocol plumbing. McpServerImpl: - Fold the built-in draft state-sync filter and the user incoming filters into a single PrependDraftStateSyncFilter(inner), removing the trivial ComposeFilters helper and the duplicate filter locals. - Drop the redundant AdoptProtocolVersionFromRequestContext helper and its two call sites; the state-sync filter already sets the negotiated protocol version before any handler runs. - Collapse the two complementary stateless-HTTP list-changed blocks into one if/else. Client transports: - Share a single TryReadJsonRpcErrorAsync helper between the streamable HTTP transport and the auto-detecting transport instead of duplicating the content-type check and body parse. - In the auto-detecting transport, throw the structured protocol exception directly once the streamable transport is adopted rather than stashing it and throwing after the try/catch. - Restore the fail-fast McpException when a server returns an InputRequiredResult carrying neither inputRequests nor requestState, instead of retrying the unchanged request until the retry cap. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The io.modelcontextprotocol/* _meta field keys lived in NotificationMethods, which is documented as holding notification method names - a category mismatch. Move them into a new ModelContextProtocol.Protocol.MetaKeys class (parallel to RequestMethods/NotificationMethods) and drop the redundant MetaKey/Key suffixes now that the class name supplies the context: MetaKeys.ProtocolVersion, ClientInfo, ClientCapabilities, LogLevel, SubscriptionId, RelatedTask. The nested 'extensions' sub-key is not a top-level _meta key, so it stays an inline literal (as with _meta) rather than a constant. RelatedTaskMetaKey shipped in the 1.3.0 baseline, so its removal from NotificationMethods is suppressed via CP0002; the other keys are unreleased and the MetaKeys additions are non-breaking. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…codes, ProtocolVersion floor (#1671)
Reconcile the SEP-2549 cacheable conformance warning with the de-drafted 2026-07-28 protocol that landed on main (modelcontextprotocol#1610, modelcontextprotocol#1671): - Gate ValidateCacheableResult on IsJuly2026OrLaterProtocol() because McpSessionHandler.DraftProtocolVersion was removed. - Update CacheableResultWarningTests for the 2026-07-28 handshake-less lifecycle: the modern client probes server/discover instead of sending initialize, so the in-memory server helper now answers server/discover for the modern path and initialize for older versions. - Rename the draft test constant to July2026ProtocolVersion ('2026-07-28'). - Document why the test file is excluded on net472.
|
Added breaking-change for the stable v1.4.1 → v2.0.0 transition. This PR changes HTTP servers to stateless mode by default and changes clients to probe server/discover before the legacy initialization handshake. Existing hosts that rely on session state, the standalone SSE stream, or server-initiated requests should explicitly configure HttpServerTransportOptions.Stateless = false; clients that must retain the legacy negotiation path can select an older protocol version. AI-generated by GitHub Copilot at the maintainer's request. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Summary
Implements the draft MCP protocol revision (2026-07-28) in the C# SDK — removing the initialize handshake and Mcp-Session-Id per SEP-2575 and SEP-2567, while preserving back-compat with legacy clients/servers via probe-and-fallback negotiation.
Stacked on the now-merged #1458 (MRTR). Opt in to draft by setting ProtocolVersion = McpSessionHandler.DraftProtocolVersion.
What's in
Protocol
Transport
Client negotiation
Public API
What's tested
Cross-SDK compatibility (Phase 7 + Phase 11d)
Validated against the other Tier-1 SDKs (TypeScript, Python, Go) in their current main / draft-branch states. Wire-trace artifacts kept in this branch's session state.
α-findings fixed in this PR (post-cross-SDK testing)
β-findings (peer-SDK issues, informational)
Conformance suite (Phase 12)
Ran the upstream @modelcontextprotocol/conformance suite against the C# SDK. Two tracks:
Track A — bump the published npm pin
Bumped tests/Common/Utils/package.json from 0.1.16 → 0.2.0-alpha.2 (d539e7fd). This activates 5 previously-gated test classes (ClientConformanceTests.RunConformanceTest_Sep2243, ServerConformanceTests.RunConformanceTest_HttpHeaderValidation, ServerConformanceTests.RunConformanceTest_HttpCustomHeaderServerValidation, ServerConformanceTests.RunMrtrConformanceTest, CachingConformanceTests.RunCachingConformanceTest).
Because 0.2.0-alpha.2 still emits the placeholder wire version DRAFT-2026-v1 (the spec-aligned 2026-07-28 only landed in unpublished alpha.3), a wire-version-match gate (HasMatchingDraftWireVersion() in tests/Common/Utils/NodeHelpers.cs, commit f3698c71) is ANDed into each draft-only HasXxx skip predicate so the 14 draft-scenario rows skip cleanly under the published alpha.2 instead of failing with mismatched-string assertions.
Track B — local build of compat/conformance-draft
Assembled a local compat/conformance-draft branch in modelcontextprotocol/conformance (tip 50ad0fa) by merging the following SEP-relevant open PRs on top of main:
#310 (SEP-2549 absence-assert) was skipped — too-deep conflict with main's RunContext refactor (PRs #319 / #317 / #321 / #318). Deferred to a follow-up.
Installed locally with npm install --no-save H:\modelcontextprotocol\conformance. ⚠️ Note: npm ci reverts to pinned alpha.2; reviewers reproducing locally must re-run the path-install after dependency restore. Flipped 3 --spec-version DRAFT-2026-v1 references in ServerConformanceTests.cs + 1 in CachingConformanceTests.cs to 2026-07-28 (commit d9277e0c), and renamed 6 tools + 1 prompt in IncompleteResultTools.cs / IncompleteResultPrompts.cs to match conformance's rename of incomplete-result-* → input-required-result-* (mirrors the SDK's MRTR IncompleteResult → InputRequiredResult rename).
Outcome (serial run on stateless HTTP):
Modes: only stateless HTTP exercised so far. Stateful HTTP and stdio modes deferred to a follow-up — Track B already validates draft conformance on the most important transport, and the published-pin gate (Track A) ensures CI on pinned alpha.2 keeps working without local conformance-build dependencies.
Parallel-run flakiness: CachingConformanceTest shows a port-pool collision (port 301x range) under parallel xUnit collections; passes consistently in isolation in under 2 s. Documented as known-flaky-in-parallel; the test suite was not switched to serial.
Out of scope
Resolved during review (originally punted, now done in this PR)
Punted to follow-up PRs