C# SDK: migrate to ModelContextProtocol 2.1.0 (MCP spec 2026-07-28) - #34
C# SDK: migrate to ModelContextProtocol 2.1.0 (MCP spec 2026-07-28)#34PederHP wants to merge 2 commits into
Conversation
Bump the SDK dependency from 1.1.0 to 2.1.0 across all projects and
align the implementation with the 2.x surface and behavior:
- Drop the gateway's backend Tasks-capability suppression and its test;
the experimental Tasks API moved out of SDK core into a separate,
wire-incompatible extension package.
- Override the new abstract McpClient.ResolveInputRequestsAsync (MRTR)
in the test doubles. MRTR forwarding itself is out of scope; the
gateway's server reports IsMrtrSupported == false.
- Remove all MCPEXP001 pragmas: ServerCapabilities.Extensions is stable
in 2.x. Keep MCPEXP002 (subclassing) pragmas.
- Keep proxying the spec-deprecated logging/setLevel and
resources/subscribe for down-level connections with targeted MCP9005
suppressions (SEP-2577/SEP-2575); on 2026-07-28 connections the SDK
rejects these methods at the session layer, so the affected tests pin
protocolVersion 2025-06-18 via a new fixture parameter.
- Harden tools/list against the now-required Tool.inputSchema: the
gateway fetches the backend result as raw JSON and coerces missing or
invalid schemas to the spec-default {"type":"object"} before and
after the interceptor chain (EnsureToolInputSchemas), so down-level
backends and schema-stripping interceptors can't fail the request.
- Add tests: end-to-end schema-stripping coercion, EnsureToolInputSchemas
units, and 2026-07-28 protocol-version negotiation through the gateway
(implicitly covering server/discover). 105 tests, zero warnings.
- Update CLAUDE.md and ARCHITECTURE_PHASES.md accordingly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR updates the C# interceptor gateway to work with ModelContextProtocol SDK 2.1.0, including new protocol behaviors and stricter tool schema validation.
Changes:
- Bump ModelContextProtocol dependency to 2.1.0 across library, tests, and samples.
- Update gateway tools/list proxying to tolerate missing/invalid
Tool.inputSchemaby coercing to the spec default. - Adjust tests/docs for protocol-version pinning and deprecated logging/resources behaviors in newer specs.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| csharp/sdk/tests/ModelContextProtocol.Interceptors.Tests/ModelContextProtocol.Interceptors.Tests.csproj | Updates test project MCP package version to 2.1.0. |
| csharp/sdk/tests/ModelContextProtocol.Interceptors.Tests/McpInterceptorGatewayTests.cs | Adds protocol negotiation + schema-coercion tests; adds protocol-version pinning for deprecated endpoints; updates test fixture to pass protocol version. |
| csharp/sdk/tests/ModelContextProtocol.Interceptors.Tests/InterceptorChainTests.cs | Removes experimental pragma usage around Extensions capability setup. |
| csharp/sdk/tests/ModelContextProtocol.Interceptors.Tests/GatewayComponentsTests.cs | Adds unit tests for EnsureToolInputSchemas; updates deprecated logging warnings; removes experimental pragmas. |
| csharp/sdk/src/ModelContextProtocol.Interceptors/ModelContextProtocol.Interceptors.csproj | Updates library MCP package version to 2.1.0. |
| csharp/sdk/src/ModelContextProtocol.Interceptors/McpServerInterceptorBuilderExtensions.cs | Removes experimental pragma usage around Extensions capability advertisement. |
| csharp/sdk/src/ModelContextProtocol.Interceptors/Gateway/GatewayProxyConfigurator.cs | Switches tools/list to raw-JSON request, adds EnsureToolInputSchemas, and wraps deprecated logging with warning pragmas. |
| csharp/sdk/src/ModelContextProtocol.Interceptors/Gateway/GatewayInterceptorProtocolBridge.cs | Removes experimental pragma usage around Extensions capability discovery/advertisement. |
| csharp/sdk/samples/TransparentProxySample/TransparentProxySample.csproj | Updates sample MCP package version to 2.1.0. |
| csharp/sdk/samples/InterceptorServerSample/InterceptorServerSample.csproj | Updates sample MCP package version to 2.1.0. |
| csharp/sdk/samples/InterceptorClientSample/InterceptorClientSample.csproj | Updates sample MCP package version to 2.1.0. |
| csharp/sdk/samples/GatewaySample/GatewaySample.csproj | Updates sample MCP package version to 2.1.0. |
| csharp/sdk/samples/GatewayChainSample/GatewayChainSample.csproj | Updates sample MCP package version to 2.1.0. |
| csharp/sdk/samples/ConfigDrivenGatewaySample/ConfigDrivenGatewaySample.csproj | Updates sample MCP package version to 2.1.0. |
| csharp/sdk/samples/AvatarMoodInterceptorSample/AvatarMoodInterceptorSample.csproj | Updates sample MCP package version to 2.1.0. |
| csharp/sdk/docs/ARCHITECTURE_PHASES.md | Updates architecture notes to stop claiming Tasks mirroring. |
| csharp/sdk/CLAUDE.md | Updates docs for SDK 2.1.0/spec 2026-07-28, deprecations, and new schema-coercion behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [Fact] | ||
| public async Task Gateway_NegotiatesLatestProtocolVersion() | ||
| { | ||
| await using var fixture = await GatewayTestFixture.CreateAsync( | ||
| backendConfigure: (options) => | ||
| { | ||
| options.Capabilities ??= new(); | ||
| options.Capabilities.Tools ??= new(); | ||
| options.Handlers.ListToolsHandler = (request, ct) => | ||
| new ValueTask<ListToolsResult>(new ListToolsResult { Tools = [] }); | ||
| options.Handlers.CallToolHandler = (request, ct) => | ||
| new ValueTask<CallToolResult>(new CallToolResult()); | ||
| }); | ||
|
|
||
| Assert.Equal("2026-07-28", fixture.ProxyClient.NegotiatedProtocolVersion); | ||
| } |
There was a problem hiding this comment.
I think this is actually what we want (i.e. purposefully setting to a version)
| var clientOptions = protocolVersion is null ? null : new McpClientOptions { ProtocolVersion = protocolVersion }; | ||
| var client = await McpClient.CreateAsync(clientTransport, clientOptions); |
| // Without the gateway coercing the stripped inputSchema back to the spec default, | ||
| // deserializing the mutated payload would fail and the request would error out. | ||
| var tools = await fixture.ProxyClient.ListToolsAsync(); | ||
| Assert.Single(tools); | ||
| Assert.Equal("stripped-tool", tools[0].Name); | ||
| } |
| var backendNode = await _backend.SendRequestAsync<ListToolsRequestParams, JsonNode>( | ||
| RequestMethods.ToolsList, mutatedParams, _jsonOptions, cancellationToken: ct); | ||
| EnsureToolInputSchemas(backendNode); | ||
| var result = JsonSerializer.Deserialize<ListToolsResult>(backendNode, _jsonOptions)!; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (2)
csharp/sdk/tests/ModelContextProtocol.Interceptors.Tests/McpInterceptorGatewayTests.cs:117
- ListTools_InjectsDefaultInputSchemaWhenInterceptorStripsIt doesn’t assert that the default inputSchema was actually injected (only that the call didn’t throw and the tool name matches). This can allow regressions where tools/list succeeds but the schema remains missing/incorrect.
// Without the gateway coercing the stripped inputSchema back to the spec default,
// deserializing the mutated payload would fail and the request would error out.
var tools = await fixture.ProxyClient.ListToolsAsync();
Assert.Single(tools);
Assert.Equal("stripped-tool", tools[0].Name);
}
csharp/sdk/src/ModelContextProtocol.Interceptors/Gateway/GatewayProxyConfigurator.cs:355
- EnsureToolInputSchemas uses listToolsPayload?["tools"], which will throw InvalidOperationException if an interceptor (or a buggy backend) returns a non-object JSON root (JsonArray/JsonValue). Since this helper is meant as defensive hardening, it should first verify the payload is a JsonObject before indexing properties.
if (listToolsPayload?["tools"] is not JsonArray tools)
{
return;
}
Summary
Bumps the MCP C# SDK dependency from 1.1.0 to 2.1.0 across all 9 projects and aligns the implementation with the 2.x surface and the 2026-07-28 spec — not just a version bump.
Compile fixes
Capabilities.Tasks = nullsuppression and its test: the experimental Tasks API was removed from SDK core in 2.0 (moved to a separate, wire-incompatibleModelContextProtocol.Extensions.Taskspackage).ResolveInputRequestsAsync(new abstract MRTR member onMcpClient) overrides to the test doubles. MRTR forwarding is deliberately out of scope for this PR; the gateway's server reportsIsMrtrSupported == false.MCPEXP001pragmas —ServerCapabilities.Extensions(which carriesio.modelcontextprotocol/interceptors) is stable in 2.x.MCPEXP002subclassing pragmas are kept.Spec deprecations (SEP-2577 / SEP-2575)
logging/setLevelandresources/subscribefor down-level clients/backends, with targetedMCP9005suppressions.2026-07-28connections the SDK rejects these methods at the session layer (replaced by per-request_metalog levels andsubscriptions/listen), so the affected tests now pinprotocolVersion: "2025-06-18"through a new fixture parameter — making them genuine down-level-compatibility tests.Defensive hardening
Tool.inputSchemais required and validated on deserialization in 2.x. The gateway'sListToolsHandlernow fetches the backendtools/listresult as raw JSON and runsGatewayProxyConfigurator.EnsureToolInputSchemasbefore and after the interceptor chain, coercing missing/invalid schemas to the spec-default{"type":"object"}so down-level backends and schema-stripping interceptors can't fail the request.Tests & docs
EnsureToolInputSchemasunit cases, and2026-07-28protocol-version negotiation through the gateway (implicitly exercisingserver/discover).CLAUDE.mdgains an "SDK 2.x / spec 2026-07-28 notes" section;ARCHITECTURE_PHASES.mdno longer listsTasksas a mirrored capability.Test plan
dotnet build— 0 errors, 0 warnings (library, tests, all 7 samples)dotnet test— 105/105 passing (was 98: −1 removed Tasks test, +8 new)🤖 Generated with Claude Code