Skip to content

MCP streamable-HTTP client matches a response on id with no method guard #1050

Description

@Vasanthdev2004

internal/mcp/network_client.go:219 matches a decoded frame to the in-flight call on id alone:

if !rpcIDMatches(message.ID, id)

There is no check that the frame is a response rather than a server-initiated request, and decodeResponse returns whatever JSON object the body holds. So a streamable-HTTP server that answers a tools/call POST with {"jsonrpc":"2.0","id":1,"method":"roots/list","params":{}} produces err = nil with an empty result: the caller sees a tool that returned nothing rather than a protocol error.

Found while closing #924, which fixed the same class in the stdio dispatcher via isRequestOrNotification() in readLoop (7c50a9f). Filing separately because the severity is different and the stdio issue should not stay open on it.

Scope, so nobody over-reads this:

  • It is not misdelivery. networkClient.request takes client.mu and holds it across the whole POST (network_client.go:200-201), so there is exactly one call in flight and the body is that call's reply. Nothing lands on the wrong caller.
  • The worst outcome is a silently empty result where an error would be correct, which is a diagnosability problem rather than a correctness or security one.
  • Both SSE paths already have the guard: deliverEventMessage (network_client.go:542) and decodeSSERPCMessage (:639). This is the plain JSON body path only.

Fix: reuse the same isRequestOrNotification() predicate before the id match and return a protocol error naming the unexpected method. A regression driving an httptest server that answers a tools/call POST with a method-bearing frame would pin it; the shape of TestStdioClientIgnoresServerInitiatedRequestsInPendingResponses transfers directly.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingissue-approvedReviewed and approved by the core team; community PRs may implement this issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions