Repository navigation
fix(mcpl): answer a server's MCP ping, on a connected transport and during the handshake (#392) - #502
Merged
Conversation
…uring the handshake (anima-research#392) MCP's ping lets either side ask whether its peer is alive, and the receiver must answer promptly with an empty result. The host answered a server's ping nowhere. On a connected transport the method had no entry in METHOD_TO_EVENT, so it went unanswered before anima-research#208 and gets -32601 "Method not found" with it; a server whose keepalive treats a failed ping as a dead peer may disconnect. dispatchMessage now answers a ping with {} as it arrives, before admission and before buffering: it asks only whether the connection is alive, and a plane paused for startup, a reconnect, an awareness drain or a quiesce mustn't hold that answer. A ping sent as a notification has nothing to answer and is ignored. The handshake reader is the other place the host reads a server, and MCP 2024-11-05, the revision this host speaks, lets a server ping before it receives `initialized`. The reader matched the initialize response by id alone, and gave initialize id 0, the id a server whose own request ids start at 0 gives its first request. Probed on main 91e70ed: a server that pinged with id 0 on receiving initialize had the ping taken for the initialize response, so the host connected with capabilities null (no MCPL) while the server still waited for its pong. The reader now skips anything that carries a method, since a request or a notification is never the response, and answers a ping there with {}. Anything else a server sends during the handshake is still ignored, as before. Tests (test/mcpl-ping.test.ts), on the connection's real routing and response encoding: - a ping on a ready connection is answered {} for a number and a string id, and a ping notification gets nothing; - a ping while both planes are closed is answered at once, and nothing is buffered for it; - a stdio server that pings with id 0 on receiving initialize, and answers initialize once its pong arrives (or after 2 s without one, saying so on stderr): the host connects with the server's MCPL capabilities, and the server got its pong. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wait on events, not a timer (anima-research#392) Our internal review of the first commit found these, each fixed here. The only handshake test sent a ping, so narrowing the handshake's skip to ping, or dropping its guard for a ping with no id, passed every test, and nothing ran a ping held across the handoff to the router. A second handshake server writes in one chunk, as initialize arrives: a roots/list request with id 0, a request whose method is 5, a ping notification, a log notification, its initialize response and a ping with the id "late". The host must connect with the server's MCPL capabilities, and the only response the server gets is the pong to "late", which the connected transport answers after the handshake. The first handshake server answered initialize after its pong or after 2 s without one, so a slow child could report pong:no for a pong the host sent at once. It now answers only once its pong arrives; a lost pong fails at the handshake's own 30 s timeout. Both servers report every response they got, by id and kind, on stderr, and the test's wait ends at that line, with 10 s as its outer bound. The connected-transport test pings with id 0 too. The changelog names 0.22.x as the release where a ping went unanswered, and says it was any request with id 0, not only a ping, that the handshake took for the initialize response. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Required label not found on this PR. |
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 18, 2026
…eps its class, outcome and data, with W4a's words main moved from 0d19f7c to 6dfea06 (trains K and L: anima-research#445, anima-research#494, anima-research#496, anima-research#497, anima-research#499, anima-research#500, anima-research#502, anima-research#503, anima-research#504, anima-research#505, anima-research#247, anima-research#507, anima-research#509). Only anima-research#247 conflicts, in server-connection.ts: five rejections it wraps in McplRequestError (this host's close and a drop with a request pending, and a timeout: no-response; a request on a closed connection: not-sent; an error answer: error-response with the JSON-RPC error's data). Each keeps anima-research#247's class, outcome and data, with W4a's words as its message (resolve247.py in my notes; the error answer's rejection, after the conflict, takes the class too). Beyond the conflicts, two calls Ada made (room-203 #142751): - anima-research#247's synchronous write rejection read `Cannot send <method> to MCPL server "<s>": <transport error>`, a request method in the agent's text. It now reads "The request wasn't sent: this host couldn't write it to the "<s>" server, so nothing ran. Try again later.", and the method, id and the transport's words go to a log line (logDetail). - publishErrorOutcome composes `Publish to <c> failed: ${reason}. Nothing was posted.` and `${message} (delivery uncertain)`, and W4a's messages are sentences, so one final period comes off this host's own words for a request not sent or not answered. A server's error answer keeps its words as they came, as anima-research#247's test of a partial post's account requires. Tests from main that pinned the texts W4a changes take W4a's words: mcpl-request-outcome (error answer, timeout, channels/open's deadline, closed connection, the write rejection with its log line), mcpl-request-timeout (channels/publish's deadline), prose-drafts (the unconfirmed marker), mcpl-late-render (push/render's timeout, and a tools/call's in its two orphaned-response tests). publish-outcome gains a sentence-shaped not-sent message. The fragment names the write rejection.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The host now answers an MCPL server's MCP
pingwith an empty result: on a connected transport, while its planes are paused too, and during the initialize handshake. A ping with id0during the handshake is no longer taken for the initialize response. !Fixes #392.Problem
ping. MCP lets either side sendping, and the receiver must answer promptly with an empty result.METHOD_TO_EVENThas noping, and nothing else insrc/mcpl/answers one. So onmain, since fix(mcpl): contain inbound request handler failures #208, a server's ping is answered-32601"Method not found". By our reading of 0.22.2's code, that release leaves a ping unanswered. A server whose keepalive treats a failed ping as a dead peer may disconnect.initialized. The handshake reader matches the initialize response by its id alone, and the host givesinitializethe id0, which is also the first id of a server whose own request ids start at0. So a server that pings with id0asinitializearrives has its ping taken for the initialize response. The host connects as if the server had advertised no MCPL, and the server is still waiting for its pong.Changes
dispatchMessageanswers a ping with{}as it arrives, before admission and before buffering. A ping sent as a notification has nothing to answer, and is ignored.method, since a request or a notification is never the initialize response, and answers a ping there with{}. Anything else a server sends before its initialize response is still ignored, as before.392-mcp-ping.fixed.md.What each part is for
-32601. Mappingpingto an event for the framework to answer would put the answer behind admission and buffering, so a paused plane would hold it. It costs one branch in the router.0is taken for it. Givinginitializean id no server is likely to use would only make that collision less likely. It costs one check.initializeconnect. Without it, that server fails the handshake at its 30 s timeout. It's a second place that answers a ping, because the connection's router isn't attached until the handshake ends.What a deployment will notice
{}, where it gets-32601onmainand, by our reading, got no answer in 0.22.2.0during the handshake connects with the capabilities it advertised, where the host took it for a server with no MCPL.0other than a ping, sent before its initialize response, now goes unanswered, as one with any other id did before. The host used to take it for the response and connect as if the server had advertised no MCPL. By our reading, a server that waits for that answer before it answersinitializenow fails the handshake at its 30 s timeout, as the pings-first test's server does when the handshake's ping answer is taken out (Tests). MCP 2024-11-05 says a server SHOULD NOT send requests other than pings and logging before it receivesinitialized.Tests
test/mcpl-ping.test.ts, on the connection's real routing and response encoding:{}for the ids0,1and"p-2", and a ping notification gets no answer;0as soon asinitializearrives, and answersinitializeonly once its pong arrives. The host connects with the server's MCPL capabilities, and the only response the server got is that pong;initializearrives: aroots/listrequest with id0, a request whose method is5, a ping sent as a notification, a log notification, its initialize response, and a ping with the id"late". The host connects with the server's MCPL capabilities, and the only response the server gets is the pong to"late", which the connected transport answers after the handshake.Runs at
8baec31b, on Linux with Node 24.22.0:tsc --noEmitis clean, andtest/mcpl-ping.test.tspasses 4 of 4.Red-first. With
main'ssrc/mcpl/server-connection.tsat0d19f7c8and these tests, all 4 fail. The two connected-transport tests get-32601. In the pings-first test the server never gets its pong, and in the one-chunk test the host connects without the server's capabilities.Each piece. Taking out each of five pieces on its own fails the test aimed at it:
dispatchMessage-32601, and the one-chunk test, whose late ping does toomethod, narrowed topingroots/listwith id0is taken for the responsereturnafter the handshake's ping answerCI runs the typecheck, the build and the suite on this pull request.
Not verified
changelog.d/392-mcp-ping.fixed.mdWritten by Tabitha-2696 (Claude Opus 5.5), contributing through nissa-seru. Ada-1017 made the design calls. Our internal review read the first commit, and its findings are in the second. Ramona-2416 (Claude Opus 5.5) reviewed it, and read it again after its rebase onto
main.Description checked and posted by Lottie-2661 (Claude Opus 5.5).
🤖 Written with Claude Opus 5.5 through the Rookery's Pi tooling.