Skip to content

Commit c4662a2

Browse files
halter73Copilot
andcommitted
Gate built-in ping handler to legacy protocol versions
The ping RPC was removed in the draft 2026-07-28 revision (SEP-2575). TypeScript and Go SDKs both reject ping under draft; C# was responding to ping unconditionally with the comment 'must always be handled', which was a holdover from the older spec. The built-in handler now throws McpProtocolException(MethodNotFound) for any per-request protocol version >= DraftProtocolVersion, falling back to the session-level NegotiatedProtocolVersion when the per-request _meta is absent (legacy sessions). Liveness on draft sessions belongs to transport- and request-level timeouts, not a removed MCP RPC. Adds PingProtocolGatingTests covering: * Ping under draft returns MethodNotFound * Ping under legacy still succeeds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c2878b7 commit c4662a2

2 files changed

Lines changed: 70 additions & 2 deletions

File tree

src/ModelContextProtocol.Core/McpSessionHandler.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,25 @@ public McpSessionHandler(
141141
_outgoingMessageFilter = outgoingMessageFilter ?? (next => next);
142142
_logger = logger;
143143

144-
// Per the MCP spec, ping may be initiated by either party and must always be handled.
144+
// ping was removed in the draft protocol revision (SEP-2575). Under draft, return
145+
// MethodNotFound; under legacy, the per-spec behavior is to always answer with PingResult.
146+
// Liveness on draft sessions belongs to transport- and request-level timeouts, not a
147+
// dedicated MCP RPC.
145148
_requestHandlers.Set(
146149
RequestMethods.Ping,
147-
(request, _, cancellationToken) => new ValueTask<PingResult>(new PingResult()),
150+
(request, jsonRpcRequest, cancellationToken) =>
151+
{
152+
string? perRequestVersion = jsonRpcRequest?.Context?.ProtocolVersion ?? NegotiatedProtocolVersion;
153+
if (perRequestVersion is not null &&
154+
StringComparer.Ordinal.Compare(perRequestVersion, DraftProtocolVersion) >= 0)
155+
{
156+
throw new McpProtocolException(
157+
$"Method '{RequestMethods.Ping}' is not available on protocol version '{perRequestVersion}'.",
158+
McpErrorCode.MethodNotFound);
159+
}
160+
161+
return new ValueTask<PingResult>(new PingResult());
162+
},
148163
McpJsonUtilities.JsonContext.Default.JsonNode,
149164
McpJsonUtilities.JsonContext.Default.PingResult);
150165

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using ModelContextProtocol.Client;
3+
using ModelContextProtocol.Protocol;
4+
using ModelContextProtocol.Server;
5+
6+
namespace ModelContextProtocol.Tests.Server;
7+
8+
/// <summary>
9+
/// Verifies that the built-in <c>ping</c> handler is gated by protocol version.
10+
/// SEP-2575 (the draft 2026-07-28 revision) removes <c>ping</c>; servers must
11+
/// respond with <c>-32601 MethodNotFound</c>. Legacy protocol versions still
12+
/// support <c>ping</c> per the spec.
13+
/// </summary>
14+
public sealed class PingProtocolGatingTests : ClientServerTestBase
15+
{
16+
public PingProtocolGatingTests(ITestOutputHelper testOutputHelper)
17+
: base(testOutputHelper, startServer: false)
18+
{
19+
}
20+
21+
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
22+
{
23+
}
24+
25+
[Fact]
26+
public async Task Ping_OnDraftSession_ReturnsMethodNotFound()
27+
{
28+
StartServer();
29+
await using var client = await CreateMcpClientForServer(new McpClientOptions
30+
{
31+
ProtocolVersion = McpSession.DraftProtocolVersion,
32+
});
33+
34+
var ex = await Assert.ThrowsAsync<McpProtocolException>(async () =>
35+
await client.PingAsync(cancellationToken: TestContext.Current.CancellationToken));
36+
37+
Assert.Equal(McpErrorCode.MethodNotFound, ex.ErrorCode);
38+
}
39+
40+
[Fact]
41+
public async Task Ping_OnLegacySession_StillSucceeds()
42+
{
43+
// Default server config; client pinned to 2025-11-25.
44+
StartServer();
45+
await using var client = await CreateMcpClientForServer(new McpClientOptions
46+
{
47+
ProtocolVersion = "2025-11-25",
48+
});
49+
50+
var result = await client.PingAsync(cancellationToken: TestContext.Current.CancellationToken);
51+
Assert.NotNull(result);
52+
}
53+
}

0 commit comments

Comments
 (0)