ultraworkers/claw-code · error · std::io::Error

MCP response for {method} used mismatched id: expected {id:?

Error message

MCP response for {method} used mismatched id: expected {id:?}, got {:?}

What it means

`McpStdioProcess::request` (runtime/src/mcp_stdio.rs:1314) requires the response id to equal the request id. `JsonRpcId` (mcp_stdio.rs:33) is an untagged enum {Number(u64), String, Null} with type-sensitive equality: a server echoing numeric `1` for a request sent as string `"1"` mismatches, as does any drifted value. Because request() awaits exactly one response, out-of-order delivery is not the usual cause — wrong echo is.

Source

Thrown at rust/crates/runtime/src/mcp_stdio.rs:1314

        params: Option<TParams>,
    ) -> io::Result<JsonRpcResponse<TResult>> {
        let method = method.into();
        let request = JsonRpcRequest::new(id.clone(), method.clone(), params);
        self.send_request(&request).await?;
        let response = self.read_response().await?;

        if response.jsonrpc != "2.0" {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "MCP response for {method} used unsupported jsonrpc version `{}`",
                    response.jsonrpc
                ),
            ));
        }

        if response.id != id {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "MCP response for {method} used mismatched id: expected {id:?}, got {:?}",
                    response.id
                ),
            ));
        }

        Ok(response)
    }

    pub async fn initialize(
        &mut self,
        id: JsonRpcId,
        params: McpInitializeParams,
    ) -> io::Result<JsonRpcResponse<McpInitializeResult>> {
        self.request(id, "initialize", Some(params)).await
    }

View on GitHub (pinned to 08106b0c37)

Solutions

  1. In the server, copy the request id verbatim — same JSON type and value — into the response.
  2. Prefer numeric u64 ids on the client side if the server stack loses string typing.
  3. If you control both ends, assert id equality in tests for every method.

Example fix

# before (server: hardcoded / coerced id)
{"jsonrpc":"2.0","id":1,"result":{}}          # request id was "1" (string)

# after (server: echo the request id exactly)
{"jsonrpc":"2.0","id":"1","result":{}}
Defensive patterns

Strategy: try-catch

Type guard

fn ids_compatible(sent: &JsonRpcId, got: &JsonRpcId) -> bool {
    matches!((sent, got),
        (JsonRpcId::Number(a), JsonRpcId::Number(b)) if a == b)
        || matches!((sent, got), (JsonRpcId::String(a), JsonRpcId::String(b)) if a == b)
        || matches!((sent, got), (JsonRpcId::Null, JsonRpcId::Null))
}

Try / catch

match process.request(id.clone(), method, params).await {
    Err(e) if e.kind() == io::ErrorKind::InvalidData
        && e.to_string().contains("mismatched id") => {
        // server coerces/rewrites ids: switch client to numeric u64 ids or fix server echo
    }
    other => other,
}

Prevention

When it happens

Trigger: Server coercing ids to numbers (e.g. JSON.parse then re-emit, or Python int('1')); server echoing a hardcoded id like 0 or 1 for every response; server sending a notification-shaped response with a fresh id; responding to a server-initiated request with the client's id.

Common situations: Hand-rolled MCP servers that don't propagate the request id; frameworks that auto-assign sequential ids; string/number confusion across language boundaries (JS BigInt handling, Python str vs int).

Related errors


AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18). Data as JSON: /api/errors/d43bb4e09aea4831. Report an issue: GitHub.