Add draft protocol support: sessionless + handshake-less (SEP-2575 + SEP-2567)#1610
Draft
halter73 wants to merge 33 commits into
Draft
Add draft protocol support: sessionless + handshake-less (SEP-2575 + SEP-2567)#1610halter73 wants to merge 33 commits into
halter73 wants to merge 33 commits into
Conversation
Move MRTR logic out of McpServer and McpClient base classes into their internal implementations, keeping the mockable API surface clean. Server side: - Remove McpServer.ActiveMrtrContext (was internal) - Add MRTR interception to DestinationBoundMcpServer.SendRequestAsync with task guard (SampleAsTaskAsync/ElicitAsTaskAsync bypass MRTR) - Remove MRTR branches from SampleAsync, ElicitAsync, RequestRootsCoreAsync - Task status tracking (InputRequired) now works during MRTR Client side: - Remove McpClient.ResolveInputRequestsAsync (was internal abstract) - Move MRTR retry loop into McpClientImpl.SendRequestAsync override - Replace SendRequestWithMrtrAsync with existing McpSession typed helper - Make resolve methods private on McpClientImpl Add 4 new tests for MRTR+Tasks interaction: - Task-augmented tool call with MRTR sampling - MRTR elicitation through tool call - SampleAsTaskAsync bypasses MRTR interception - MRTR tool call and task-based sampling coexist Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gate MRTR on a draft protocol version ("2026-06-XX") instead of the
experimental["mrtr"] capability. This matches how the real protocol will
work when MRTR is ratified — the protocol version IS the signal.
Changes:
- Add ExperimentalProtocolVersion property to McpClientOptions and
McpServerOptions, marked [Experimental(MCPEXP001)]
- Add ExperimentalProtocolVersion constant to McpSessionHandler
- Client: request experimental version when option is set; accept it
in server response validation
- Server: accept experimental version from client when option matches;
ClientSupportsMrtr() checks negotiated version instead of capability
- StreamableHttpHandler: accept experimental version in header validation
- Remove experimental["mrtr"] capability advertisement and
MrtrContext.ExperimentalCapabilityKey
Compatibility matrix (no failures):
- Both experimental: MRTR via IncompleteResult + retry
- Server exp, client not: Legacy JSON-RPC requests
- Client exp, server not: Negotiates to stable, retry loop is no-op
- Neither: Standard behavior
Tests:
- Update all existing MRTR tests to set ExperimentalProtocolVersion
- Add 5 new compatibility tests covering all matrix combinations
- All 1886 core + 324 AspNetCore tests pass on net10.0 and net9.0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add IncompleteResultException for tool handlers to return incomplete results with inputRequests and/or requestState directly - Add McpServer.IsMrtrSupported property for checking client compatibility - Handle IncompleteResultException in MRTR wrapper and race handler - Validate MRTR support when exception is thrown (returns JSON-RPC error if client doesn't support MRTR) - Fall through to MRTR-aware invocation for unmatched requestState retries - Add 8 protocol conformance tests (raw HTTP) for low-level MRTR flows - Add 7 integration tests for client auto-retry of low-level tools - Add MRTR concept documentation covering both high-level and low-level APIs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ith filters - Test concurrent ElicitAsync+SampleAsync throws InvalidOperationException (MrtrContext prevents concurrent server-to-client requests) - Test cancellation mid-retry stops the MRTR loop with OperationCanceledException - Test via outgoing message filters that no old-style sampling/elicitation JSON-RPC requests are sent when MRTR is active - Test that transport middleware sees IncompleteResult round-trips Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Allow IncompleteResultException to serialize as IncompleteResult in stateless mode where ClientSupportsMrtr() returns false. The low-level API is designed for stateless servers that cannot determine client MRTR support. Add 5 end-to-end tests using Streamable HTTP in stateless mode: - Elicitation, sampling, and roots individually - All three concurrent (with TCS concurrency proof barriers) - Multi-round-trip with requestState across 2 retries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add high-level and low-level MRTR examples to each feature doc: - elicitation.md: ElicitAsync (transparent) + IncompleteResultException - sampling.md: SampleAsync (transparent) + IncompleteResultException - roots.md: RequestRootsAsync (transparent) + IncompleteResultException Fix missing entries in docs navigation: - toc.yml: Add Sampling under Client Features - index.md: Add Tasks and MRTR to Base Protocol table Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move MrtrExchange and MrtrContinuation into their own files to follow the convention of one top-level class per file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Proves that outgoing/incoming message filters can track and enforce per-session MRTR flow limits using context.Server.SessionId: - OutgoingFilter_TracksIncompleteResultsPerSession: verifies count increments on IncompleteResult and decrements after retry - OutgoingFilter_CanEnforcePerSessionMrtrLimit: verifies replacing IncompleteResult with a JSON-RPC error when limit is exceeded Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion header ClientSupportsMrtr now purely reflects whether the client negotiated the MRTR protocol version, independent of server transport mode. The stateless guard is moved to the call site that gates the high-level await path (which requires storing continuations). In stateless mode, each request creates a new McpServerImpl that never sees the initialize handshake. The Mcp-Protocol-Version header is now flowed via JsonRpcMessageContext.ProtocolVersion so the MRTR wrapper can populate _negotiatedProtocolVersion, making IsMrtrSupported return true when the client sends the experimental protocol version header. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests that mirror the exact code patterns from mrtr.md and elicitation.md docs in stateless mode: - IsMrtrSupported returns false when client doesn't opt in - IsMrtrSupported check + IncompleteResultException throw (the doc pattern) works end-to-end including ElicitResult.Content access - Same pattern returns fallback when client doesn't opt in - Load shedding (requestState-only) with IsMrtrSupported guard Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add SessionDelete_CancelsPendingMrtrContinuation test verifying: - MRTR continuation is cancelled on session DELETE - Debug-level log emitted for cancelled continuations - No Error-level log noise from handler cancellation - Add SessionDelete_RetryAfterDelete_ReturnsSessionNotFound test verifying retry with stale requestState returns 404 - Add MrtrContinuationsCancelled debug log in DisposeAsync - Skip ToolCallError log for OperationCanceledException during disposal (not a tool bug, just session teardown) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Harden the MRTR (Multi Round-Trip Request) implementation to correctly handle cancellation across retries, clean shutdown, and handler lifecycle tracking. Thread-safety: - Replace mutable ExchangeTask property with immutable InitialExchangeTask and return-value data flow from ResetForNextExchange - Use Interlocked.CompareExchange in ResetForNextExchange to validate expected state, ensuring concurrent calls reliably fail - Use TrySetResult as the sole atomicity gate in RequestInputAsync, with explicit failure on concurrent exchanges - Store SourceTcs back-reference in MrtrExchange for CAS validation Cancellation: - Introduce a long-lived handler CTS (encapsulated in MrtrContinuation) that survives across retries, keeping the handler cancellable after the original request's combinedCts is disposed - Bridge each retry's cancellation to the handler CTS via CancellationTokenRegistration in AwaitMrtrHandlerAsync - Check TrySetResult/TrySetException return values on retry to detect already-cancelled exchanges - CTS is never disposed (like Kestrel's HttpContext.RequestAborted) to avoid deadlock risks from Cancel/Dispose inside synchronization primitives. CancelHandler() is the sole operation and is thread-safe. Shutdown: - Dispose session handler before iterating _mrtrContinuations so no new continuations can be created during the cleanup loop - Track MRTR handler tasks with inFlightCount + TCS drain pattern (matching McpSessionHandler.ProcessMessagesCoreAsync) so DisposeAsync waits for all handlers to complete before returning - Add ObserveHandlerCompletionAsync fire-and-forget observer that logs unhandled handler exceptions at Error level Logging: - Exclude IncompleteResultException from Error-level ToolCallError logging since it is normal MRTR control flow, not an error Simplifications: - Flow MrtrContext via JsonRpcMessageContext property instead of _pendingMrtrContexts ConcurrentDictionary with synchronous-before-await assumptions - MrtrContinuation is a lifecycle object created upfront, eliminating CTS disposal branching, orphanedCts tracking, and post-drain cleanup Tests (8 new): - ServerDisposal_CancelsHandlerCancellationToken_DuringMrtr - CancellationNotification_DuringInFlightMrtrRetry_CancelsHandler - CancellationNotification_ForExpiredRequestId_DoesNotAffectHandler - DisposeAsync_WaitsForMrtrHandler_BeforeReturning - HandlerException_DuringMrtr_IsLoggedAtErrorLevel - IncompleteResultException_IsNotLoggedAtErrorLevel Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace non-generic TaskCompletionSource (introduced in .NET 5) with TaskCompletionSource<bool> in McpClientMrtrTests.cs so the test project compiles against net472, which only has TaskCompletionSource<TResult>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add support for tools to perform ephemeral MRTR exchanges before committing to a background task. This enables a two-phase workflow: 1. Ephemeral phase: The handler uses ElicitAsync/SampleAsync via MRTR to gather user input (e.g., confirmation before expensive operations). 2. Task phase: The handler calls CreateTaskAsync() to transition to a background task, receiving a task ID and cancellation token. API surface: - McpServerToolAttribute.DeferTaskCreation property - McpServerToolCreateOptions.DeferTaskCreation property - McpServerTool.DeferTaskCreation virtual property (overridden in AIFunctionMcpServerTool and DelegatingMcpServerTool) - McpServer.CreateTaskAsync() virtual method (overridden in DestinationBoundMcpServer) Implementation: - DeferredTaskInfo carries task metadata across MRTR continuations, with signal/ack TCS pair for handler ↔ framework coordination. - ConfigureTools attaches DeferredTaskInfo to MrtrContext when DeferTaskCreation is enabled and client provides task metadata. - AwaitMrtrHandlerAsync races handler vs exchange vs task creation signal (3-way WhenAny). - HandleDeferredTaskCreationAsync creates the task, re-links the handler CTS to the task cancellation token, and acknowledges the handler so it can continue as a background task. - TrackDeferredHandlerTaskAsync tracks completion and stores results (handler already tracked by ObserveHandlerCompletionAsync for in-flight counting). If the handler returns without calling CreateTaskAsync(), a normal (non-task) result is returned to the client. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…w, fix logging Document DeferTaskCreation and CreateTaskAsync in MRTR and Tasks conceptual docs with cross-references and matching test coverage. Revert MrtrContext flow from JsonRpcMessageContext property back to _mrtrContextsByRequestId ConcurrentDictionary with try/finally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rAsync Add test verifying a tool can use the high-level MRTR elicit API then throw IncompleteResultException to drop to the low-level API in a single call. Replace try/finally with 'using var' for the CancellationTokenRegistration. Fix task/mrtr comment and use modern indexing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…acy JSON-RPC When a tool throws IncompleteResultException and the client doesn't support MRTR, the server now resolves each InputRequest by sending the corresponding standard JSON-RPC call (elicitation, sampling, roots) to the client and retries the handler with the responses. This allows authors to write a single MRTR-native tool implementation that works with any client. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The backcompat layer now returns 'without input requests' instead of 'Multi Round-Trip Requests' when IncompleteResultException has no inputRequests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mode Backcompat tests (McpClientMrtrCompatTests): - 10-retry limit enforcement (tool that never completes) - Empty inputRequests dictionary triggers immediate error - Error propagation when client handler throws during resolve Experimental mode test (McpClientMrtrTests): - Client handler throws during MRTR input resolution: exception surfaces to caller, server logs cancelled MRTR continuation on disposal. This exercises a fundamental MRTR limitation where the client has no channel to communicate input resolution failures back to the server. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
IsMrtrSupported now returns true whenever the low-level MRTR API (IncompleteResultException) can be used, not just when the client natively negotiated MRTR. The only case where it returns false is stateless mode with a non-MRTR client, where nobody can drive the retry loop. - Add IsLowLevelMrtrAvailable() to McpServerImpl - Update DestinationBoundMcpServer.IsMrtrSupported to use it - Error explicitly for stateless + non-MRTR in the IncompleteResult handler instead of silently serializing an unusable response - Add Stateless_IncompleteResultException_WithoutMrtrClient_ReturnsError test verifying the error path - Fix existing tests: stateless tests now properly negotiate MRTR, protocol test verifies IsMrtrSupported=true via backcompat Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move server-behavior tests from Client/ to Server/ and drop the misleading McpClient prefix. Most MRTR tests verify server-side behavior (handler lifecycle, backcompat resolution, low-level API) but were in Client/ because they use ClientServerTestBase. Split McpClientMrtrTests (17 tests) into: - Client/MrtrIntegrationTests: E2E round-trips and client retry (10) - Server/MrtrHandlerLifecycleTests: cancellation, disposal, logging (7) Rename and move to Server/: - McpClientMrtrCompatTests → MrtrBackcompatTests - McpClientMrtrLowLevelTests → MrtrLowLevelApiTests - McpClientMrtrMessageFilterTests → MrtrMessageFilterTests - McpClientMrtrSessionLimitTests → MrtrSessionLimitTests - McpClientMrtrWithTasksTests → MrtrTaskIntegrationTests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add McpClient + Stateful Streamable HTTP integration tests for MRTR, and a test verifying experimental protocol version negotiation. Fix the sampling log assertion in MapMcpTests to handle MRTR mode where sampling is embedded in the IncompleteResult exchange. Demote MrtrHandlerError from Error to Debug to avoid double-logging since the observer just confirms the exception was observed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a shared ServerMessageTracker utility that records outgoing server-to- client JSON-RPC request methods via an outgoing message filter. Wire it into all 8 MRTR test classes and call AssertNoLegacyMrtrRequests() in ~35 experimental-mode tests to verify no legacy elicitation/create or sampling/createMessage requests are sent. Simplify MrtrMessageFilterTests by replacing its manual ConcurrentBag-based tracking with the shared tracker (-29 lines). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ification Fix McpSessionHandler.SendRequestAsync to route through the outgoing message filter pipeline instead of calling SendToRelatedTransportAsync directly. This makes server-originated JSON-RPC requests (elicitation/create, sampling/createMessage, roots/list) visible to outgoing filters, matching the documented behavior that filters see all outgoing messages. Enhance ServerMessageTracker to use both incoming and outgoing filters for comprehensive MRTR protocol mode verification: - Outgoing: detects IncompleteResult responses (result_type=incomplete) and legacy JSON-RPC requests - Incoming: detects MRTR retries (requests with inputResponses or requestState) Add AssertMrtrUsed() to ~35 experimental MRTR tests and AssertMrtrNotUsed() to ~15 backward-compatibility tests, ensuring every MRTR test verifies the correct protocol mode was used. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor MRTR tests to provide extensive coverage with less code, making it easy for reviewers and other SDK implementers to see edge case coverage at a glance. Test consolidation: - Move MRTR tests into MapMcpTests.Mrtr.cs partial class, which runs every test across StreamableHttp, SSE, and Stateless transports - Use Theory tests with (experimentalServer, experimentalClient) bools to cover all 4 MRTR/backcompat protocol combinations - Delete MrtrBackcompatTests.cs, StatelessMrtrTests.cs, and StreamableHttpMrtrTests.cs (coverage subsumed by MapMcpTests) - Reduce MrtrLowLevelApiTests.cs and MrtrProtocolTests.cs to unique protocol-level tests not covered by McpClient-based tests - Update MrtrIntegrationTests doc to reflect edge-case focus and add simple happy-path smoke test for reviewer reference Test improvements: - Add ServerMessageTracker with AssertMrtrUsed()/AssertMrtrNotUsed() to verify correct protocol mode in every test - Add NegotiatedProtocolVersion assertions to verify negotiation - Add result.IsError checks on all success-path tool results - Add ErrorCode assertions on all error-path tests - Tighten all version assertions to exact values (no NotEqual) Helpers: - ConnectAsync takes Action<McpClientOptions>? to prevent bypassing transport config (path/TransportMode defaults) - ConfigureExperimentalServer/ConfigureDefaultServer with distinct Implementation names for clear test output - ConfigureMrtrHandlers configures elicitation, sampling, and roots handlers on an existing McpClientOptions object - ConnectExperimentalAsync/ConnectDefaultAsync for common patterns - Move EnablePollingAsync stateless test to MapMcpStatelessTests - Add client-side warnings for legacy requests on MRTR sessions and IncompleteResult on non-MRTR sessions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tion When the caller's CancellationToken triggers an OperationCanceledException during a pipe write, re-throw it instead of wrapping it in IOException. This ensures callers get the expected OperationCanceledException when they cancel their own token. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the protocol-level changes for the draft revision (SEP-2575 stateless MCP and SEP-2567 sessionless MCP): - New _meta keys for per-request protocolVersion / clientInfo / clientCapabilities / logLevel - New RPCs: server/discover and subscriptions/listen, plus the acknowledgement notification - New JSON-RPC error codes -32004 (UnsupportedProtocolVersion) and -32003 (MissingRequiredClientCapability) with typed exception classes - Client skips initialize under draft mode, calls server/discover instead, and falls back to legacy initialize when the server doesn't support the experimental version - Server keeps the legacy initialize handler for back-compat, and a new built-in incoming message filter projects per-request _meta values onto the per-session client info/capabilities/version state under draft - HTTP server suppresses Mcp-Session-Id and routes draft requests through the stateless path regardless of HttpServerTransportOptions.Stateless - HTTP server returns -32004 with a structured supportedVersions data payload when a client requests an unsupported protocol version - HTTP client transport carries the protocol version header on every request (sourced from per-request _meta when present), and surfaces -32004/-32003 from HTTP error responses as typed McpProtocolException for the connection logic to react Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds round-trip tests for the new draft protocol revision and updates the XML documentation on ExperimentalProtocolVersion (client + server) to describe the full SEP-2575 + SEP-2567 behavior (sessionless, handshake-less, server/discover, MRTR-only server-to-client interactions, fallback to legacy initialize on unsupported-version responses). Tests added: - DiscoverProtocolTests / SubscriptionsListenProtocolTests / DraftErrorDataTests: JSON-serialization round-trip coverage for the new protocol types and error data payloads. - DraftConnectionTests: end-to-end client/server connection flow for draft client vs. draft server, draft client vs. legacy server (fallback), legacy client vs. draft server, and explicit server/discover invocation. - DraftHttpHandlerTests (AspNetCore): HTTP-level checks that draft requests don't emit Mcp-Session-Id, unsupported protocol versions return -32004 with the structured supportedVersions payload, draft requests carrying an Mcp-Session-Id route through the legacy lookup, and draft GET/DELETE are rejected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1458 (
halter73/mrtr). Do not merge until that PR lands.What
Adds C# SDK support for the protocol-level changes introduced by the draft MCP revision:
initializehandshake; per-request_metacarries protocol version, client info, and client capabilities; introducesserver/discoverandsubscriptions/listen; new error codes-32004(UnsupportedProtocolVersion) and-32003(MissingRequiredClientCapability).Mcp-Session-Idheader and the session concept from the protocol; state lives in explicit handles passed as tool arguments.The feature is gated on the existing
ExperimentalProtocolVersionsetting ("2026-06-XX"placeholder), which already gates MRTR (#1458). A single server can service legacy clients (initialize + Mcp-Session-Id) and draft clients (server/discover + sessionless) concurrently.Changes
Protocol types & constants
RequestMethods.ServerDiscover,RequestMethods.SubscriptionsListenNotificationMethods.SubscriptionsAcknowledgedNotification_metakey constants:ProtocolVersionMetaKey,ClientInfoMetaKey,ClientCapabilitiesMetaKey,LogLevelMetaKey,SubscriptionIdMetaKeyMcpErrorCode.UnsupportedProtocolVersion = -32004,McpErrorCode.MissingRequiredClientCapability = -32003DiscoverRequestParams/DiscoverResultSubscriptionsListenRequestParams(withSubscriptionsListenNotifications) +SubscriptionsAcknowledgedNotificationParamsUnsupportedProtocolVersionErrorData/MissingRequiredClientCapabilityErrorDataUnsupportedProtocolVersionException/MissingRequiredClientCapabilityException(typedMcpProtocolExceptionderivatives with structured error data round-tripping)Client (
McpClientImpl)ConnectAsyncnow skipsinitialize/notifications/initializedwhenExperimentalProtocolVersionis set; callsserver/discoverinstead to populateServerCapabilities/ServerInfo/ServerInstructions.initialize(with the highest mutually-supported version) when the server doesn't support the experimental version — either becauseserver/discoveris missing (MethodNotFound), the server returned-32004, or thesupportedVersionsreply doesn't include the requested version._metavia the sharedMcpSessionHandler.InjectDraftMetahelper. IncompleteResult retries preserve the_meta.Server (
McpServerImpl)ConfigureDiscover(always registered) andConfigureSubscriptions(basic listen handler that emits the acknowledgement notification and keeps the stream open until cancelled).CreateDraftStateSyncFilterprojects per-request_metavalues onto the per-session_negotiatedProtocolVersion/_clientCapabilities/_clientInfostate for draft clients. Legacy clients still populate viainitializeand the filter is a no-op for them.McpSessionHandler.PopulateContextFromMetaparses_metaearly in incoming-message processing and projects the values ontoJsonRpcMessageContext.{ProtocolVersion, ClientInfo, ClientCapabilities, LogLevel}so filters and handlers can read them without re-parsing. Throws-32602if the per-request_metaprotocol version disagrees with the transport-level header (per SEP-2575).Streamable HTTP (
StreamableHttpHandler)MCP-Protocol-Versionheader) without anMcp-Session-Idis routed through the stateless path regardless ofHttpServerTransportOptions.Stateless— sessions are not created and theMcp-Session-Idresponse header is suppressed.Mcp-Session-Idstill flow through the legacy session lookup, preserving back-compat for clients that opted into the experimental version on top of the stateful MRTR model (the MRTR PR's tests rely on this).400with a useful message pointing tosubscriptions/listen.ValidateProtocolVersionHeadernow returns-32004(UnsupportedProtocolVersion) with a structured{supported: [...], requested}payload instead of a plain400.Streamable HTTP client transport
MCP-Protocol-Versionheader on every request — sourced from the outgoing request's_metafield when present (the first draft request,server/discover, doesn't have a previously-negotiated version yet).-32004and-32003from HTTP error responses and re-throws them as the typedMcpProtocolExceptionderivatives so the connection logic can react. Other JSON-RPC errors carried in 4xx/5xx bodies (e.g.,403,404 session-not-found) continue to surface asHttpRequestExceptionfor back-compat.Tests
DiscoverProtocolTests/SubscriptionsListenProtocolTests/DraftErrorDataTests— JSON-serialization round-trips for the new types.DraftConnectionTests— end-to-end coverage for draft client × draft server (negotiates draft), draft client × legacy server (falls back), legacy client × draft server, and explicitserver/discovercalls from legacy clients.DraftHttpHandlerTests(AspNetCore) —Mcp-Session-Idsuppression,-32004structured-data response shape, the back-compat path for draft requests with a session id, andGET/DELETErejection under draft.Test results
halter73/mrtr). 27 skipped, including 4 new skips for await-style MRTR scenarios that fundamentally require session affinity (draft mode is sessionless, so the high-level continuation tracker can't span requests; the low-levelIncompleteResultExceptionpath still works).LegacyRequestOnMrtrSession_LogsWarning) was updated to expectserver/discoverinstead ofinitializefrom a draft client.Out of scope (deliberate follow-ups)
McpClient.SubscribeToListenAsync— a high-level client method for openingsubscriptions/listenand a draft-mode rewrite of the resource-subscribe convenience methods. The server-side handler exists; the client just doesn't have a typed wrapper yet.ILogger→subscriptions/listencompat shim — server-side log notifications outside a request flow through any activesubscriptions/listensubscription's_meta/logLevel. Requires the client method first.server.ElicitAsync/server.SampleAsynccontinuations don't span requests under draft mode because the server is forced stateless per request. Could be re-enabled with arequestState-keyed continuation lookup; documented as a skip in the relevant tests for now.[Obsolete]on legacy SDK surface — explicitly out of scope; both legacy and draft paths coexist without deprecation annotations./sseand/messagefor legacy back-compat.Spec status
The draft revision (where SEP-2575 and SEP-2567 are merged) doesn't yet have a finalized protocol version label. The SDK uses the existing placeholder
"2026-06-XX"; swap when the real label lands.Risk
ExperimentalProtocolVersion. Existing legacy paths (initialize,Mcp-Session-Id, GET stream,Last-Event-ID,resources/subscribe,ping,logging/setLevel) remain functional and untouched.-32004or-32003JSON-RPC body now throws a typedMcpProtocolExceptionfrom the streamable HTTP client transport instead ofHttpRequestException. Other error codes are unchanged. This was scoped down from a broader behavior change after observing theMessages_FromNewUser_AreRejectedregression.