From b4037bf428e828e2d06eb2545ffe7a90ec149a35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:32:56 +0000 Subject: [PATCH 1/3] Initial plan From c1bd65f0b55d4d20aa7c8be46c3ec84c84f709f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:37:34 +0000 Subject: [PATCH 2/3] Register ping handler in McpClientImpl so server-initiated pings succeed Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Client/McpClientImpl.cs | 6 ++++++ .../Client/McpClientTests.cs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs index 8317656d0..ae726e825 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs @@ -85,6 +85,12 @@ private void RegisterHandlers(McpClientOptions options, NotificationHandlers not notificationHandlers.RegisterRange(notificationHandlersFromOptions); } + requestHandlers.Set( + RequestMethods.Ping, + (request, _, cancellationToken) => new ValueTask(new PingResult()), + McpJsonUtilities.JsonContext.Default.JsonNode, + McpJsonUtilities.JsonContext.Default.PingResult); + if (taskStatusHandler is not null) { notificationHandlers.Register( diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs index 32e04da60..9a99f597c 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs @@ -782,4 +782,16 @@ public async Task SetLoggingLevelAsync_WithRequestParams_NullThrows() await Assert.ThrowsAsync("requestParams", () => client.SetLoggingLevelAsync((SetLevelRequestParams)null!, TestContext.Current.CancellationToken)); } + + [Fact] + public async Task ServerCanPingClient() + { + await using McpClient client = await CreateMcpClientForServer(); + + var pingRequest = new JsonRpcRequest { Method = RequestMethods.Ping }; + var response = await Server.SendRequestAsync(pingRequest, TestContext.Current.CancellationToken); + + Assert.NotNull(response); + Assert.NotNull(response.Result); + } } From 115a907063b2f25e2cfa460961f258e1ad1a283a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:56:39 +0000 Subject: [PATCH 3/3] Move ping handler into McpSessionHandler, removing it from McpServerImpl and McpClientImpl Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- src/ModelContextProtocol.Core/Client/McpClientImpl.cs | 6 ------ src/ModelContextProtocol.Core/McpSessionHandler.cs | 8 ++++++++ src/ModelContextProtocol.Core/Server/McpServerImpl.cs | 9 --------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs index ae726e825..8317656d0 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs @@ -85,12 +85,6 @@ private void RegisterHandlers(McpClientOptions options, NotificationHandlers not notificationHandlers.RegisterRange(notificationHandlersFromOptions); } - requestHandlers.Set( - RequestMethods.Ping, - (request, _, cancellationToken) => new ValueTask(new PingResult()), - McpJsonUtilities.JsonContext.Default.JsonNode, - McpJsonUtilities.JsonContext.Default.PingResult); - if (taskStatusHandler is not null) { notificationHandlers.Register( diff --git a/src/ModelContextProtocol.Core/McpSessionHandler.cs b/src/ModelContextProtocol.Core/McpSessionHandler.cs index 4d9cb01ba..b2f94fb28 100644 --- a/src/ModelContextProtocol.Core/McpSessionHandler.cs +++ b/src/ModelContextProtocol.Core/McpSessionHandler.cs @@ -132,6 +132,14 @@ public McpSessionHandler( _incomingMessageFilter = incomingMessageFilter ?? (next => next); _outgoingMessageFilter = outgoingMessageFilter ?? (next => next); _logger = logger; + + // Per the MCP spec, ping may be initiated by either party and must always be handled. + _requestHandlers.Set( + RequestMethods.Ping, + (request, _, cancellationToken) => new ValueTask(new PingResult()), + McpJsonUtilities.JsonContext.Default.JsonNode, + McpJsonUtilities.JsonContext.Default.PingResult); + LogSessionCreated(EndpointName, _sessionId, _transportKind); } diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 39feae5d6..49fadb5e4 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -91,7 +91,6 @@ public McpServerImpl(ITransport transport, McpServerOptions options, ILoggerFact ConfigureLogging(options); ConfigureCompletion(options); ConfigureExperimental(options); - ConfigurePing(); // Register any notification handlers that were provided. if (options.Handlers.NotificationHandlers is { } notificationHandlers) @@ -204,14 +203,6 @@ public override async ValueTask DisposeAsync() await _sessionHandler.DisposeAsync().ConfigureAwait(false); } - private void ConfigurePing() - { - SetHandler(RequestMethods.Ping, - async (request, _) => new PingResult(), - McpJsonUtilities.JsonContext.Default.JsonNode, - McpJsonUtilities.JsonContext.Default.PingResult); - } - private void ConfigureInitialize(McpServerOptions options) { _requestHandlers.Set(RequestMethods.Initialize,