Merge pull request #541 from koic/respond_to_server_pings_in_the_client
Answer server-to-client pings in the client
This commit is contained in:
commit
6d272df0e2
2 changed files with 421 additions and 0 deletions
|
|
@ -1118,6 +1118,19 @@ module MCP
|
|||
},
|
||||
}
|
||||
end
|
||||
elsif message["method"] == MCP::Methods::PING && (message["id"].is_a?(String) || message["id"].is_a?(Numeric))
|
||||
# The ping receiver "MUST respond promptly with an empty response" (MCP ping utility),
|
||||
# so an unhandled ping is answered with the empty result rather than Method not found.
|
||||
# A JSON-RPC id is a String or a Number; the reference SDKs reject other shapes at
|
||||
# schema validation, so a ping carrying one falls through to Method not found instead.
|
||||
# The default lives here instead of in `@server_request_handlers`, whose `any?` opens
|
||||
# the GET listening stream on connect: a built-in pong must not change listener behavior.
|
||||
# A handler registered via `on_server_request("ping")` still wins through the branch above.
|
||||
{
|
||||
jsonrpc: JsonRpcHandler::Version::V2_0,
|
||||
id: message["id"],
|
||||
result: {},
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonrpc: JsonRpcHandler::Version::V2_0,
|
||||
|
|
|
|||
|
|
@ -436,6 +436,18 @@ module MCP
|
|||
|
||||
parsed = JSON.parse(line.strip)
|
||||
|
||||
# A frame carrying `method` is a request or notification, never the awaited response,
|
||||
# whatever its id says: JSON-RPC id spaces are per sender, so a server-chosen ping id
|
||||
# may legitimately collide with the awaited one. Pings are answered;
|
||||
# other server-to-client requests over stdio stay unsupported as documented,
|
||||
# and notifications carry no id.
|
||||
if parsed.is_a?(Hash) && parsed.key?("method")
|
||||
# A JSON-RPC id is a String or a Number; the reference SDKs reject other shapes at
|
||||
# schema validation, so a ping carrying one is skipped rather than echoed back.
|
||||
answer_ping(parsed) if parsed["method"] == MCP::Methods::PING && json_rpc_id?(parsed["id"])
|
||||
next
|
||||
end
|
||||
|
||||
# A JSON-RPC message is an object; skip a non-object frame (array or scalar)
|
||||
# the same way as a frame without an id.
|
||||
next unless parsed.is_a?(Hash) && parsed.key?("id")
|
||||
|
|
@ -451,6 +463,22 @@ module MCP
|
|||
)
|
||||
end
|
||||
|
||||
# The ping receiver "MUST respond promptly with an empty response". Answered inline from
|
||||
# the read loop, so a keepalive cannot stall unanswered while a response is awaited;
|
||||
# between requests nothing reads stdout, and a queued ping is answered on the next read.
|
||||
def answer_ping(parsed)
|
||||
@write_mutex.synchronize do
|
||||
write_message({ jsonrpc: JsonRpcHandler::Version::V2_0, id: parsed["id"], result: {} })
|
||||
end
|
||||
rescue RequestHandlerError
|
||||
# Best effort: a pong cannot be delivered over a broken stdin, and the failure must not
|
||||
# surface as an error of the unrelated request whose response the loop is reading.
|
||||
end
|
||||
|
||||
def json_rpc_id?(id)
|
||||
id.is_a?(String) || id.is_a?(Numeric)
|
||||
end
|
||||
|
||||
def ensure_running!
|
||||
return if @wait_thread.alive?
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue