Description
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:
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
mcp-go/server/streamable_http.go
Lines 392 to 397 in e744c19
- SSE: Ping message correctly includes an ID field
Lines 399 to 405 in e744c19
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.