Skip to content

Migrate to mcp stateless protocol SDK Phase 1 & 2.#3016

Open
g2vinay wants to merge 27 commits into
microsoft:mainfrom
g2vinay:migrate-to-mcp-stateless-protocol
Open

Migrate to mcp stateless protocol SDK Phase 1 & 2.#3016
g2vinay wants to merge 27 commits into
microsoft:mainfrom
g2vinay:migrate-to-mcp-stateless-protocol

Conversation

@g2vinay

@g2vinay g2vinay commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Migrates Azure MCP Server and Microsoft.Mcp.Core to the MCP 2026-07-28 stateless protocol
using the C# beta SDK (ModelContextProtocol / ModelContextProtocol.AspNetCore 2.0.0-preview.1).

The revision removes the initialize/initialized handshake and protocol-level
Mcp-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

  • SDK upgrade: 1.1.02.0.0-preview.1; adapted breaking RequestContext / tool-list
    serialization APIs; suppressed the MCP9005 sampling-obsolete diagnostic where sampling stays
    a backward-compatible enhancement.
  • Stateless HTTP routing (SEP-2243): version-gated middleware requiring Mcp-Method on
    2026-07-28 POSTs and Mcp-Name only for named methods (tools/call, resources/read,
    prompts/get); legacy 2025-11-25 clients pass through unchanged.
  • Elicitation/consent (MRTR): consent wrapper maps accept/decline/cancel and forwards the
    requested schema; production path runs on the request-scoped server and fails closed when the
    client lacks elicitation support.
  • Discovery/cache: server identity/instructions stay startup-owned; capability discovery is
    request-driven (server/discover, tools/list); internal tool cache annotated as a server-side
    optimization only.
  • Observability: W3C trace context (traceparent/tracestate/baggage) captured from
    _meta with spec-compliant tag names; support logging remains strictly local.
  • Outbound OAuth: RegistryServerProvider validates HTTPS + non-empty scopes; docs corrected
    (RFC 8707) and clarified that registry servers use Azure Identity tokens, not the SDK OAuth flow.
  • Session affinity: repositioned as optional/legacy-only; not required by the default runtime.

g2vinay added 15 commits July 7, 2026 10:17
- 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.
@g2vinay g2vinay self-assigned this Jul 9, 2026
g2vinay added 3 commits July 9, 2026 09:50
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.
@g2vinay g2vinay marked this pull request as ready for review July 9, 2026 22:46
@g2vinay g2vinay requested review from a team as code owners July 9, 2026 22:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/list works without initialize across 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

g2vinay added 3 commits July 9, 2026 16:17
- 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 RickWinter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added feedback, CI is also failing

@RickWinter RickWinter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some more feedback

Comment thread docs/Authentication.md
Comment thread core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Runtime/McpRuntime.cs Outdated

@RickWinter RickWinter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread core/Microsoft.Mcp.Core/src/Commands/TelemetryConstants.cs Outdated
@g2vinay

g2vinay commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Added feedback, CI is also failing

Failure is due to a stale recording on main, fixing it in another PR.

g2vinay added 4 commits July 14, 2026 16:09
…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.
@g2vinay g2vinay requested review from arunrab and micha31r as code owners July 15, 2026 01:25
@micha31r

Copy link
Copy Markdown
Contributor

Hi @g2vinay, thanks for the update. We'll plan a migration away from sampling.
cc @arunrab

@vukelich vukelich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

https://github.com/microsoft/mcp/pull/2926/changes#diff-735be9ea554cec106ecd374cdda09045ee7d64b127af23268524c55a0b68dd77R62

@RickWinter RickWinter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants