From 7e507f2963a24709c5c62800bf1d5ccd8cc10736 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sat, 12 Sep 2026 00:02:30 +0900 Subject: [PATCH] [Doc] Document the client's ping answers and the stdio request limit ## Motivation and Context The client answers server pings automatically, but the docs leave out where that applies and what the stdio transport does with the other server-to-client requests. The client Transports page now lists the automatic ping answer among the things the stdio transport handles, and a note states that `ping` is the only server-to-client request answered there: a wire-level `elicitation/create` or `sampling/createMessage` is ignored over stdio, so a server that sends one waits for an answer that never comes, while the SEP-2322 `input_required` route works on every transport and keeps `on_elicitation` and `on_sampling` handlers working over stdio. Without this note, the only trace of the limitation is a hung tool call. The client Ping page now says that over HTTP a ping reaches the client only on a stream that is already open and that the client never opens one to receive pings, so a server can only ping a client that has a stream open. Registering `on_server_request("ping")` moves to the end of the section as the advanced option it is, together with its side effect of opening the GET listening stream on `connect`. The client overview mentions the automatic answer next to `MCP::Client#ping`, and the server Ping page's Client Side section points at it. ## How Has This Been Tested? Documentation only; the linked headings and pages exist. ## Breaking Changes None. --- docs/_client/index.md | 3 ++- docs/_client/ping.md | 8 ++++++-- docs/_client/transports.md | 8 ++++++++ docs/_server/ping.md | 2 ++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/_client/index.md b/docs/_client/index.md index c9196225..2e3077c9 100644 --- a/docs/_client/index.md +++ b/docs/_client/index.md @@ -17,7 +17,8 @@ This class supports: - Lifecycle negotiation and connection via `MCP::Client#connect`, adopting the modern lifecycle when the server serves it; see [Lifecycle](/client/lifecycle/) - Server discovery via the `server/discover` method (`MCP::Client#discover`); see [Explicit Discovery](/client/lifecycle/#explicit-discovery) -- Liveness check via the `ping` method (`MCP::Client#ping`) +- Liveness check via the `ping` method (`MCP::Client#ping`), and automatic answers to server pings; + see [Answering Server Pings](/client/ping/#answering-server-pings) - Tool listing via the `tools/list` method (`MCP::Client#tools`) - Tool invocation via the `tools/call` method (`MCP::Client#call_tool`) - Resource listing via the `resources/list` method (`MCP::Client#resources`) diff --git a/docs/_client/ping.md b/docs/_client/ping.md index f6889e9d..f546afea 100644 --- a/docs/_client/ping.md +++ b/docs/_client/ping.md @@ -32,13 +32,17 @@ propagate as exceptions raised by the transport layer. ## Answering Server Pings On handshake-lifecycle connections a server may ping the client the same way, and the client answers automatically with -the empty result - no handler is needed. Registering `transport.on_server_request("ping")` on `MCP::Client::HTTP` replaces -the automatic answer. +the empty result - no handler is needed. Over HTTP a ping reaches the client only on a stream that is already open +(the SSE response of an in-flight POST, or the GET listening stream); the client never opens a stream just to receive pings. Over stdio, a ping that arrives while a response is awaited is answered inline; between requests it is answered when the next request starts reading. The answer is best effort: a pong that cannot be written (for example, over a broken pipe) is dropped rather than failing the request whose response is being read. Independently of pings, consider setting `read_timeout:` on `MCP::Client::Stdio`, since a server that never answers otherwise holds the read until the process exits. +To answer pings yourself, register `transport.on_server_request("ping")` on `MCP::Client::HTTP`; it replaces +the automatic answer and, like any handler registered there, opens the standalone GET listening stream on `connect`, +as described on the [Transports](/client/transports/#server-to-client-requests-elicitation) page. + ## Server Side How servers answer `ping` requests and ping the client themselves is documented on diff --git a/docs/_client/transports.md b/docs/_client/transports.md index a30259c8..43626665 100644 --- a/docs/_client/transports.md +++ b/docs/_client/transports.md @@ -59,6 +59,14 @@ The stdio transport automatically handles: - Spawning the server process with `Open3.popen3` - MCP protocol initialization handshake (`initialize` request + `notifications/initialized`) - JSON-RPC 2.0 message framing over newline-delimited JSON +- Answering server `ping` requests; see [Answering Server Pings](/client/ping/#answering-server-pings) + +{: .note } +> `ping` is the only server-to-client request answered over stdio. A wire-level `elicitation/create` +> or `sampling/createMessage` is ignored on this transport, so a server that sends one waits for an answer +> that never comes; use Streamable HTTP for those, as described below. The SEP-2322 `input_required` route, +> which is how the modern lifecycle asks for the same input, works on every transport, so `on_elicitation` +> and `on_sampling` handlers still fire over stdio; see [Multi Round-Trip Requests](/client/mrtr/). ## HTTP Transport Layer diff --git a/docs/_server/ping.md b/docs/_server/ping.md index 62720627..d8c6627c 100644 --- a/docs/_server/ping.md +++ b/docs/_server/ping.md @@ -48,3 +48,5 @@ Server-to-client requests are bounded by a timeout on the Streamable HTTP transp ## Client Side Pinging the server with `MCP::Client#ping` is documented on the client [Ping](/client/ping/) page. +The Ruby client answers the pings sent with `ServerSession#ping` automatically; +see [Answering Server Pings](/client/ping/#answering-server-pings).