Skip to content

stdio server: malformed-_meta 2026 request as first message gets no error response (connection wedges silently) #1157

Description

@blacktop

Summary

When the first message on a stdio connection is a non-initialize request whose _meta is missing part of the MCP 2026-07-28 required key set (e.g. it has io.modelcontextprotocol/protocolVersion but no io.modelcontextprotocol/clientCapabilities), the server sends no bytes at all to the client: no JSON-RPC error, no response to any subsequent — fully valid — request. From the client's perspective the connection is silently wedged.

The same malformed request sent after successful negotiation is correctly answered with -32602, so clients get a diagnostic in one ordering and silence in the other.

Version

rmcp 3.1.2.

Where it happens

serve_server_with_ct_inner (src/service/server.rs): the pre-negotiation loop breaks out with the first non-ping request, and when it is neither InitializeRequest nor a request with the complete 2026 inline-metadata key set, it returns

return Err(ServerInitializeError::ExpectedInitializeRequest(Some(
    ClientJsonRpcMessage::request(request, id),
)));   // src/service/server.rs:536 (also :521 for non-request messages)

That error goes to the server-side caller of serve(); nothing is written to the transport before the future fails. Compare the sibling paths in the same function, which do reply before proceeding/failing: pre-init ping gets an EmptyResult, and a complete-_meta discover-lifecycle request gets its response (or a JSON-RPC error) via transport.send(...).

Reproduction

Any rmcp stdio server. Pipe these two lines to it (first request is missing clientCapabilities in _meta; the second is fully valid):

printf '%s\n%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}' \
  '{"jsonrpc":"2.0","id":2,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"t","version":"0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \
  | your-rmcp-stdio-server

Observed: zero bytes of output; the valid server/discover (id 2) is never answered. If the host keeps running despite the failed serve future, the client hangs forever.

Control: send the valid server/discover first and the malformed request second — the discover completes and the malformed request is answered with -32602.

Expected

Before failing the serve future, the server should write a JSON-RPC error for the offending request id (e.g. -32600/-32602 with a message like "expected initialize request or complete 2026 request metadata; missing: io.modelcontextprotocol/clientCapabilities"), so a misconfigured 2026 client learns why it is being disconnected instead of timing out. missing_required_keys already computes the exact missing-key list at the decision point (src/service/server.rs:533), so the error message can name them.

Impact

Any MCP 2026 client whose opening request omits one of the required _meta keys (easy to do while implementing SEP-2575) hangs with no diagnostic on either side of the wire. We hit this in ida-mcp integration testing; on the server side the only symptom is a failed serve future.

Metadata

Metadata

Assignees

Labels

2026-07-28P1High: significant functionality gap or spec violationT-serviceService layer changesT-transportTransport layer changesbugSomething is not workingready for workIssue is well-defined and ready to be picked up

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions