Migrate to mcp stateless protocol SDK Phase 1 & 2.#3016
Conversation
…add negative HTTP test
- Add ServerModeCoverageTests: stdio stateless tools/list for single, namespace, and all modes without initialize handshake (Workstream I item 5) - Add Mcp-Session-Id absence assertion to HTTP positive test in ConsolidatedModeTests (Workstream I item 7 / Workstream B): server must not set Mcp-Session-Id under the 2026-07-28 stateless protocol
Add negative test that sends Mcp-Method: tools/call in headers but tools/list in the JSON body. Under the 2026-07-28 protocol header/body agreement is required for explicit gateway routing; server must respond with a 4xx client error for a mismatch.
- Add W3C trace context support (traceparent, tracestate, baggage) to _meta handling - Capture trace context fields in both CallToolHandler and ListToolsHandler - Add W3C trace context tags to telemetry constants (w3c.traceparent, w3c.tracestate, w3c.baggage) - Add comprehensive test for W3C trace context propagation in HTTP mode - Verified Azure telemetry enrichment remains compatible with new trace context capture
…g deprecation Workstream E: Sampling deprecation and deterministic routing - Add telemetry tracking for sampling vs deterministic resolution (resolution.method, sampling.attempted, sampling.succeeded, resolution.confidence) - Implement DeterministicToolResolution utility with token-based matching and Levenshtein distance for command/tool inference - Support deterministic fallback when sampling is unavailable or deprecated - Add 8 comprehensive unit tests validating tool name and command matching - Fix pre-existing RequestContext constructor issue in ToolLoaderTelemetryTests Strategy: Keep sampling as optional enhancement while building deterministic path parity through telemetry measurement. Enables Phase 3 sampling deprecation roadmap.
- Record Workstream B item 4 decision: session-affinity package remains optional for explicit legacy/custom state-routing scenarios only - Keep package out of default Azure MCP runtime assumptions - Update operator guidance to header-first stateless routing for MCP 2026-07-28 (Mcp-Method/Mcp-Name) - Clarify when not to enable session affinity in distributed package README
- Add consolidated-mode HTTP integration test for server/discover with MCP 2026-07-28 routing headers - Validate discovery succeeds without initialize handshake - Validate stateless response does not emit Mcp-Session-Id - Record Workstream C progress in migration proposal
…h validation Phase 2 (Workstream B / SEP-2243): gate Mcp-Method/Mcp-Name enforcement on the 2026-07-28 protocol version and require Mcp-Name only for named methods (tools/call, resources/read, prompts/get), preserving legacy 2025-11-25 HTTP interop. Correct and extend ConsolidatedMode header tests (named-method rejection, non-named-method acceptance, legacy initialize interop). Workstream G: add outbound OAuth validation to RegistryServerProvider, remove misleading dead issuer-binding branch, and correct the OAuth resource-parameter RFC citation (RFC 8707) in Authentication.md. Workstream I item 6: add ServerToolLoader external-server discovery and proxy-routing fallback tests. Docs: record Phase 1/2 and Workstream G verification findings in the migration proposal.
The beta SDK (2.0.0-preview.1) added a private protected abstract member (ResolveInputRequestsAsync) to McpClient, making it impossible to proxy with NSubstitute or subclass from a test assembly; every Substitute.For<McpClient>() threw TypeLoadException at mock-creation time. Add a reusable LoopbackMcpClient test helper that builds a real McpClient over an in-process loopback transport (forcing the legacy 2025-11-25 handshake) and answers requests via a responder. Convert all McpClient mock sites (MockMcpClientBuilder, MockMcpDiscoveryStrategyBuilder, ServerToolLoaderTests, SingleProxyToolLoaderTests, BaseDiscoveryStrategyTests) to the loopback client, and observe client disposal via a transport-backed tracker. Also fix three pre-existing migration test gaps surfaced by the full run: - ServerStartCommandTests.InvalidMode: expect ClientTransportClosedException (wrapping IOException), the SDK's new early-transport-failure type. - ClientToolTests ping: assert ping is unavailable on the 2026-07-28 protocol (method name retained for recorded-session matching). - McpRuntimeTests null-parameter test: pass null Params so the runtime null-guard is exercised. Core, Microsoft.Mcp.Core, and Azure.Mcp.Server test suites are green.
Normalize over-indented lines flagged by dotnet format (WHITESPACE) that were introduced during the SDK RequestContext API migration edits.
ClientTransportClosedException.InnerException is platform-dependent (IOException on Windows, null on Linux CI). Assert only the outer ClientTransportClosedException, which is thrown consistently and already proves the invalid mode fails to start the server.
There was a problem hiding this comment.
Pull request overview
This PR advances the repository’s migration to the MCP stateless protocol (2026-07-28) by updating the SDK dependency, adding HTTP routing-header validation, and hardening server/tool-loading behavior with new/updated tests across Azure and Fabric servers.
Changes:
- Update MCP SDK dependencies (ModelContextProtocol 2.0 preview) and adjust core/test code to new request/transport behaviors.
- Add/expand integration and coverage tests to ensure
tools/listworks withoutinitializeacross server modes and HTTP root endpoint semantics. - Introduce deterministic (non-sampling) tool/command resolution fallback and expand observability (_meta → W3C trace tags) plus elicitation schema forwarding.
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| servers/Fabric.Mcp.Server/tests/Fabric.Mcp.Server.Tests/Infrastructure/ServerStartupTests.cs | Adds HTTP root-endpoint tools/list test and expands stdio tests for stateless/legacy interop. |
| servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ServerModeCoverageTests.cs | New tests gating stateless tools/list coverage across single/namespace/all modes. |
| servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ConsolidatedModeTests.cs | Expands consolidated-mode tests for stateless HTTP routing headers, meta forwarding, and negative cases. |
| docs/Authentication.md | Documents inbound/outbound auth implications and external OAuth compliance notes for MCP 2026-07-28. |
| Directory.Packages.props | Bumps ModelContextProtocol(+AspNetCore) to 2.0 preview and updates Microsoft.Extensions.AI.Abstractions. |
| core/Microsoft.ModelContextProtocol.HttpServer.Distributed/README.md | Clarifies distributed/session affinity package as optional for stateless protocol compliance. |
| core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/ToolLoaderTelemetryTests.cs | Updates RequestContext construction to match new SDK patterns. |
| core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/DeterministicToolResolutionTests.cs | Adds unit coverage for deterministic tool/command resolution behavior. |
| core/Microsoft.Mcp.Core/src/Models/Elicitation/ElicitationRequestParams.cs | Updates docstring to reflect schema forwarding behavior. |
| core/Microsoft.Mcp.Core/src/Extensions/McpServerElicitationExtensions.cs | Forwards requested schema into protocol elicitation request; adds cancel action support. |
| core/Microsoft.Mcp.Core/src/Commands/TelemetryConstants.cs | Adds new telemetry tag constants for trace context and resolution metadata. |
| core/Microsoft.Mcp.Core/src/Areas/Server/ServerJsonContext.cs | Adjusts source-generated JSON context registrations (removes ListToolsResult envelope type). |
| core/Microsoft.Mcp.Core/src/Areas/Server/RegistryServerServiceCollectionExtensions.cs | Adds/updates documentation for OAuth-protected registry server HTTP client registration. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/SingleProxyToolLoader.cs | Switches tools JSON serialization to an array payload; scopes sampling pragmas for migration. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/ServerToolLoader.cs | Adds sampling pragmas and keeps request-path behavior aligned with migration constraints. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/NamespaceToolLoader.cs | Adds sampling pragmas and keeps JSON serialization aligned with new tools payload shape. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/DeterministicToolResolution.cs | New deterministic lexical resolver for tool/command selection when sampling is unavailable. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/CompositeToolLoader.cs | Updates RequestContext usage and clarifies caching is server-side only. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/BaseToolLoader.cs | Wraps sampling handler hookup with migration pragma. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs | Adds HTTP middleware to validate stateless routing headers for 2026-07-28 requests. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs | Clarifies startup-owned metadata vs request-driven discovery under stateless protocol. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Runtime/McpRuntime.cs | Captures W3C trace context from params._meta into telemetry tags. |
| core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Discovery/RegistryServerProvider.cs | Adds OAuth config validation (HTTPS + non-empty scopes) and documents protocol hardening intent. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Extensions/McpServerElicitationExtensionsTests.cs | Adds tests verifying schema forwarding and cancel/decline action mapping. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/ClientToolTests.cs | Updates ping behavior expectations for the negotiated 2026-07-28 protocol. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/ServerStartCommandTests.cs | Updates failure-mode assertion to SDK’s new transport exception shape. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Helpers/MockMcpDiscoveryStrategyBuilder.cs | Switches default substitute client to loopback transport for sealed McpClient. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Helpers/MockMcpClientBuilder.cs | Replaces McpClient proxying with loopback-backed real client and updated tools serialization. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Helpers/LoopbackMcpClient.cs | New loopback transport + dispose tracking helper for tests under sealed McpClient. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/SingleProxyToolLoaderTests.cs | Updates RequestContext patterns and replaces client mocking with loopback transport. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/ServerToolLoaderTests.cs | Adds external-server proxy router tool tests and loopback-based tools/list/call stubs. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/RegistryToolLoaderTests.cs | Disables MCP migration warnings for updated SDK usage. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/NamespaceToolLoaderTests.cs | Updates RequestContext construction patterns for new SDK. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/CompositeToolLoaderTests.cs | Updates RequestContext patterns and null-param tests under new SDK. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/CommandFactoryToolLoaderTests.cs | Updates RequestContext construction patterns and null-param tests under new SDK. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ToolLoading/BaseToolLoaderTests.cs | Disables MCP migration warnings due to new SDK APIs. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/Runtime/McpRuntimeTests.cs | Updates RequestContext patterns and preserves null-param guard coverage. |
| core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/Discovery/BaseDiscoveryStrategyTests.cs | Replaces McpClient substitutes with loopback client + disposal tracking assertions. |
Comments suppressed due to low confidence (1)
core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Discovery/RegistryServerProvider.cs:35
- The XML doc includes a entry, but the primary constructor has no tokenCredentialProvider parameter. With TreatWarningsAsErrors enabled, this will produce a CS1572/CS1573 XML-doc warning that fails the build. Remove the extra entry or update it to match the actual constructor parameters.
/// <param name="id">The unique identifier for the server.</param>
/// <param name="serverInfo">Configuration information for the server.</param>
/// <param name="httpClientFactory">Factory for creating HTTP clients.</param>
/// <param name="tokenCredentialProvider">The token credential provider for OAuth authentication.</param>
public sealed class RegistryServerProvider(string id, RegistryServerInfo serverInfo, IHttpClientFactory httpClientFactory) : IMcpServerProvider
- HTTP test processes: disable stdout/stderr redirection (never read by HTTP tests) to eliminate child-process pipe-buffer hang risk. Stdio tests that read stdout keep stdout redirect but disable stderr. (Copilot microsoft#1/microsoft#2) - SendWithRetryAsync: dispose the 404 HttpResponseMessage before retrying to prevent socket/handler leaks. (Copilot microsoft#5/microsoft#6) - WriteMissingRoutingHeaderResponse: emit a proper JSON-RPC 2.0 error envelope and use JsonEncodedText.Encode + .ToString() for correct string escaping. (Copilot microsoft#3 / my F1) - RegistryServerProvider class doc: replace the misleading Authorization Hardening bullet list with accurate language — only HTTPS + non-empty scope checks are performed here; issuer/app-type/PRM checks are delegated to Azure Identity. (Copilot microsoft#4 / my F3)
…edirect fix The previous change (afb871c) set RedirectStandardError = false on all Fabric test blocks. However all three tests call process.BeginErrorReadLine() and assert on the captured stderr to detect DI errors — BeginErrorReadLine() requires RedirectStandardError = true and throws InvalidOperationException otherwise. Restored to true in all three test blocks.
RickWinter
left a comment
There was a problem hiding this comment.
Added feedback, CI is also failing
There was a problem hiding this comment.
One thing worth resolving before merge: DeterministicToolResolution.cs and its four companion telemetry constants (ResolutionMethod, SamplingAttempted, SamplingSucceeded, ResolutionConfidence) have no production caller. The class documents itself as Phase 3 Workstream E, but this is the Phase 1 and 2 PR, so it lands roughly 400 lines of unreachable public API plus tests. I would defer that to the Phase 3 change that actually wires it up. Not merge-blocking, but it is dead surface today.
Separately, Directory.Packages.props bumps Microsoft.Extensions.AI.Abstractions from 10.3.0 to 10.5.2, which the description does not mention. If that is a required transitive alignment for the new SDK, a one-line note in the PR body would help; if it is incidental, drop it to keep the migration focused.
Nothing is blocking
Failure is due to a stale recording on main, fixing it in another PR. |
…irement, not server-enforced Three places in Authentication.md overclaimed that the Azure MCP Server enforces issuer validation for outbound registry connections. The code (ValidateOAuthConfiguration) only checks HTTPS and non-empty scopes; issuer/audience validation is performed by the downstream resource via Azure Identity bearer tokens. Updated: (1) the remote auth layer description, (2) the MCP 2026-07-28 note box, (3) the OAuth requirements table — separating enforced checks (HTTPS, non-empty scopes) from deployment requirements (issuer, app-type, scope registration). Addresses RickWinter review comment.
…se 3 PR DeterministicToolResolution.cs (220 lines) and its test file (189 lines) have no production callers — all three loader paths still guard intent resolution with SupportsSampling(), so a client without sampling gets the learn response and never reaches this resolver. The four companion telemetry constants (ResolutionMethod, SamplingAttempted, SamplingSucceeded, ResolutionConfidence) are also never emitted. Per RickWinter's review, defer to the Phase 3 PR that actually wires up deterministic routing so the surface lands with its integration tests. The code is preserved in commit ae8ff11 and can be cherry-picked into the Phase 3 branch.
… telemetry
CaptureToolCallMeta accepted traceparent, tracestate, and baggage verbatim from untrusted MCP _meta fields. An attacker controlling tool-call input could stuff arbitrary data into these fields and have it written into distributed traces. - traceparent: validate against the W3C format regex (^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$) before adding the tag; silently drop malformed values. - tracestate: cap at 512 chars per W3C spec guidance; drop if longer. - baggage: removed entirely. Baggage is an unbounded cross-service propagation bag, not a trace identifier. Recording it verbatim lets callers write arbitrary key-value pairs into telemetry. Remove the TagName.Baggage constant as it is now unused.
…ed sampling APIs Azure.Mcp.Tools.Insights was added to main after this branch was cut. When CI merges the PR into main the Insights source is compiled against the upgraded SDK, which made four sampling APIs error-level obsolete (MCP9005): CreateMessageRequestParams, SamplingMessage, McpServer.SampleAsync, ClientCapabilities.Sampling. Additionally, CommandContext.McpServer and CommandContext.ProgressToken were removed from CommandContext in this migration, causing CS1061 errors in InsightsGetCommand. Fixes: - Restore McpServer? and ProgressToken? to CommandContext (neither type is deprecated; only McpServer.SampleAsync is). - Restore McpServer = request.Server / ProgressToken = request.Params.ProgressToken in CommandFactoryToolLoader and NamespaceToolLoader. - Add NoWarn>MCP9005 to the Insights src and test csproj files with a comment noting migration to MRTR is tracked as Phase 3 work. All 54 Insights unit tests pass.
vukelich
left a comment
There was a problem hiding this comment.
Quick comment. As chatted in a meeting, please help us understand what pieces we do ourselves that might be better if the .NET MCP SDK handles itself.
| } | ||
|
|
||
| private static async Task ValidateMcpRoutingHeadersMiddleware(HttpContext context, Func<Task> next) | ||
| { |
There was a problem hiding this comment.
As chatted about, let's confirm if the .NET MCP SDK covers this, and if not, let's document in-line why we do anything ourselves.
| @@ -170,6 +203,7 @@ private static void CaptureToolCallMeta(Activity? activity, JsonObject? meta) | |||
| public async ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsRequestParams> request, CancellationToken cancellationToken) | |||
| { | |||
| using var activity = _telemetry.StartActivity(ActivityName.ListToolsHandler, request.Server.ClientInfo); | |||
There was a problem hiding this comment.
Don't we need to update this to support request-level client info?
Had another PR doing a speculative update without the 2.0.0-preview.1 version:
RickWinter
left a comment
There was a problem hiding this comment.
Follow-up to the prior review: the post-review commits remove the unwired Phase 3 resolver and telemetry constants, stop exporting client-controlled baggage, validate trace context before recording it, and correct the issuer-validation documentation. I found no new issues in the later changes. Nothing blocks merge.
Migrates Azure MCP Server and
Microsoft.Mcp.Coreto the MCP 2026-07-28 stateless protocolusing the C# beta SDK (
ModelContextProtocol/ModelContextProtocol.AspNetCore2.0.0-preview.1).The revision removes the
initialize/initializedhandshake and protocol-levelMcp-Session-Id, makes every request self-describing, and adds explicit HTTP routing headers(
Mcp-Method/Mcp-Name). Azure tool business logic is unchanged; the work is in the runtime,transport, elicitation, discovery, and test layers.
Ref: Beta SDKs for the 2026-07-28 MCP Spec RC
What changed
1.1.0→2.0.0-preview.1; adapted breakingRequestContext/ tool-listserialization APIs; suppressed the
MCP9005sampling-obsolete diagnostic where sampling staysa backward-compatible enhancement.
Mcp-Methodon2026-07-28POSTs andMcp-Nameonly for named methods (tools/call,resources/read,prompts/get); legacy2025-11-25clients pass through unchanged.requested schema; production path runs on the request-scoped server and fails closed when the
client lacks elicitation support.
request-driven (
server/discover,tools/list); internal tool cache annotated as a server-sideoptimization only.
traceparent/tracestate/baggage) captured from_metawith spec-compliant tag names; support logging remains strictly local.RegistryServerProvidervalidates HTTPS + non-empty scopes; docs corrected(RFC 8707) and clarified that registry servers use Azure Identity tokens, not the SDK OAuth flow.