Repository navigation
fix(mcp): keep stdio open after a malformed opener - #334
Merged
Merged
Conversation
Bump rmcp 3.2.0 -> 3.5.0 and wrap the stdio transport so a first request lacking complete 2026-07-28 `_meta` gets -32602 and the connection stays open for the client's `initialize` fallback, instead of rmcp ending the process. Remove once rust-sdk#1248 ships.
Merged
tenequm
added a commit
that referenced
this pull request
Oct 2, 2026
## 🤖 New release * `pond-db`: 0.19.4 -> 0.19.5 <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [0.19.5](v0.19.4...v0.19.5) - 2026-10-02 ### <!-- 2 -->🐛 Bug Fixes - **devin:** group sidekick subagents, surface images, variants and phase ([#322](#322)) ([0aa49ba](0aa49ba)) Devin: persistent sidekick subagents now import as one child session instead of degrading the sync, images become typed file parts, and chat phase and variant tags appear in options.devin. Run `pond sync`; add `--verify` to enrich already-synced devin sessions. - **mcp:** keep stdio open after a malformed opener ([#334](#334)) ([bb27e33](bb27e33)) `pond mcp` no longer exits when a client's first request lacks MCP 2026-07-28 request metadata; it answers with an error and keeps the connection open, so Claude Code can fall back to `initialize` and connect normally. **Full Changelog**: v0.19.4...v0.19.5 </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/).
tenequm
added a commit
that referenced
this pull request
Oct 13, 2026
…350) ## What broke Claude Code intermittently showed pond as `connected - tools fetch failed`, with no tools and no resources: ``` request _meta is missing or has malformed required fields: io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/clientCapabilities ``` Claude Code opens a stdio server with a `server/discover` probe. `pond mcp` opens the store before it reads stdin, so on a slow open (3.3 s here, against 1.4-2.2 s on the good starts) the probe reply misses Claude Code's wait and it falls back to a classic `initialize`. Both requests then reach pond. On rmcp 3.5.0 the discover probe committed the connection to the 2026-07-28 inline lifecycle, so pond answered `initialize` at 2025-11-25 and then rejected the classic `tools/list` and `resources/list` that followed, for lacking 2026-07-28 `_meta`. Claude Code's MCP log shows the sequence: ``` Connection established ... "protocolEra":"legacy","negotiatedProtocolVersion":"2025-11-25" stdio server answered the server/discover probe after it had connected legacy ... Failed to fetch resources: request _meta is missing or has malformed required fields: ... Failed to fetch tools: request _meta is missing or has malformed required fields: ... ``` Reproduced on the released 0.19.6 two ways: a `claude -p` run against `pond mcp` behind a 4 s startup delay (no pond tools), and a script that writes discover, initialize, initialized and tools/list back to back (`tools/list` gets `-32602`). This is not the #334 failure: there the first request lacked `_meta`; here it is a complete discover probe. ## Fix - rmcp 3.5.0 -> 3.5.1. It ships [rust-sdk#1248](modelcontextprotocol/rust-sdk#1248), which keeps a discover probe from selecting a lifecycle, so a following `initialize` selects the classic one. The same change answers a malformed first request with `-32602` and keeps reading. - That second half is what `RecoverableOpener` (#334) did by hand, and its doc said to drop it once #1248 shipped, so it is removed. `serve_io` passes the plain transport again. ## Tests - New `discover_probe_then_initialize_serves_classic_requests`: discover, initialize, initialized and tools/list written back to back. `tools/list` must return tools. - `malformed_opener_leaves_initialize_fallback_open` is kept as the guard for what the wrapper covered, now against rmcp's own handling. Both tests share an `exchange` helper that writes raw frames and collects replies by id. ## Verified - `cargo test --lib transport::mcp::` (12 passed), `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`. - The back-to-back script against this build: `tools/list` returns the 4 tools (`-32602` on 0.19.6). - `claude -p` (Claude Code 2.1.296) against this build behind the same 4 s startup delay: all 4 pond tools present, and the MCP log reads "answered the server/discover probe after its bound, so it was only slow to start and speaks the new protocol". ## Release note `pond mcp` no longer loses its tools when Claude Code's discovery probe and its `initialize` fallback both reach a slow-starting server, which showed pond as connected with "tools fetch failed" until a manual reconnect. No action needed.
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.
Summary
Claude Code sometimes opens a
pond mcpstdio session with a bare request (e.g.tools/list) carrying no 2026-07-28_meta. rmcp answers it-32602and then ends the connection (ServerInitializeError::ExpectedInitializeRequest), so the client never reaches itsinitializefallback and the server shows as failed. This is unchanged through rmcp 3.5.0; the upstream fix is the still-open rust-sdk#1248.ServerInfo->ServerConfigfor the 3.4 deprecation; negotiated versions unchanged).RecoverableOpenerwraps the stdio transport until the first accepted message: a non-initialize request lacking complete_meta(rmcp's ownmissing_required_keyscheck, same error text) is answered-32602and reading continues. ping,initialize, complete-_metarequests and everything after the opener pass straight to rmcp, so its lifecycle and version gates are untouched.serve_iois shared byserve_stdioand the MCP test helper, so the existing discovery/classic tests now run through the wrapper.malformed_opener_leaves_initialize_fallback_open: malformed opener ->-32602->initialize(2025-06-18 and 2026-07-28) ->tools/listsucceeds. Fails without the wrapper.Remove the wrapper once rust-sdk#1248 ships.
Release note
pond mcpno longer exits when a client's first request lacks MCP 2026-07-28 request metadata; it answers with an error and keeps the connection open, so Claude Code can fall back toinitializeand connect normally.