Skip to content

bug: ID missing in Ping request when using streamable HTTP #351

Closed
@bcaldwell

Description

@bcaldwell

Description

The ping request when using streamable HTTP transport doesn't include an ID field, which causes compatibility issues with TypeScript clients such as MCP Inspector and Cursor. The error occurs because these clients expect ping request messages to have an ID field, but the current ping implementation in streamable HTTP omits this field (it is coming through as null), while the SSE implementation correctly includes it.

Code Sample

package main

import (
        "fmt"
        "time"

        "github.com/mark3labs/mcp-go/server"
)

func main() {
        s := server.NewMCPServer(
                "Demo",
                "1.0.0",
                server.WithToolCapabilities(false),
        )

        server := server.NewStreamableHTTPServer(s, server.WithHeartbeatInterval(time.Second*10))
        fmt.Println("Starting mcp server on http://localhost:8080/mcp")
        if err := server.Start(":8080"); err != nil {
                fmt.Printf("Server error: %v\n", err)
        }
}

Logs or Error Messages

To view the error I used MCP inspector, bu connecting it to http://localhost:8080/mcp. After the connection is made in the UI the error log start.

❯ npx @modelcontextprotocol/inspector
Starting MCP inspector...
⚙️ Proxy server listening on port 6277
🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀
Received POST message for sessionId undefined
New streamable-http connection
Query parameters: [Object: null prototype] {
  url: 'http://localhost:8080/mcp',
  transportType: 'streamable-http'
}
Connected to Streamable HTTP transport
Connected MCP client to backing server transport
Created streamable web app transport 2377eec8-83a6-4518-acfb-e1001f8ab463
Received POST message for sessionId 2377eec8-83a6-4518-acfb-e1001f8ab463
Received GET message for sessionId 2377eec8-83a6-4518-acfb-e1001f8ab463
Error from MCP server: ZodError: [
  {
    "code": "unrecognized_keys",
    "keys": [
      "id"
    ],
    "path": [],
    "message": "Unrecognized key(s) in object: 'id'"
  }
]
    at get error [as error] (file:///Users/b.caldwell/.npm/_npx/5a9d879542beca3a/node_modules/zod/dist/esm/v3/types.js:51:31)
    at ZodUnion.parse (file:///Users/b.caldwell/.npm/_npx/5a9d879542beca3a/node_modules/zod/dist/esm/v3/types.js:126:22)
    at processStream (file:///Users/b.caldwell/.npm/_npx/5a9d879542beca3a/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:169:66)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  issues: [
    {
      code: 'unrecognized_keys',
      keys: [Array],
      path: [],
      message: "Unrecognized key(s) in object: 'id'"
    }
  ],
  addIssue: [Function (anonymous)],
  addIssues: [Function (anonymous)],
  errors: [
    {
      code: 'unrecognized_keys',
      keys: [Array],
      path: [],
      message: "Unrecognized key(s) in object: 'id'"
    }
  ]
}

A similar error can be found in cursor:
Image

Environment

  • Go version (see go.mod): 1.24.1
  • mcp-go version (see go.mod): 0.30.1
  • MCP Inspector v0.13.0
  • Node.js v20.19.0
  • Any other relevant environment details: macos/arm

Additional Context

The issue stems from ping implementations for streamable http not including an ID field. It works if I use SSE server. Comparing the implimentation

  • Streamable HTTP: Ping message lacks an ID field
    message := mcp.JSONRPCRequest{
    JSONRPC: "2.0",
    Request: mcp.Request{
    Method: "ping",
    },
    }
  • SSE: Ping message correctly includes an ID field

    mcp-go/server/sse.go

    Lines 399 to 405 in e744c19

    message := mcp.JSONRPCRequest{
    JSONRPC: "2.0",
    ID: mcp.NewRequestId(session.requestID.Add(1)),
    Request: mcp.Request{
    Method: "ping",
    },
    }

This causes TypeScript clients that validate request structure to reject the ping messages from streamable HTTP servers, breaking the connection. In the typescript SDK the request ID schema is defined as not allowing null values:

/**
 * A uniquely identifying ID for a request in JSON-RPC.
 */
export const RequestIdSchema = z.union([z.string(), z.number().int()]);

Possible Solution

I believe the last used request ID needs to be added to the session implementation of streamable http in a similar way to how sse is doing it. Doing this would enable sending the ping request with the next id.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions