Skip to content

fix: pass req.body to StreamableHTTP transport on POST /mcp#1

Open
chipitaps wants to merge 1 commit into
keptlive:mainfrom
chipitaps:fix-mcp-body-parser
Open

fix: pass req.body to StreamableHTTP transport on POST /mcp#1
chipitaps wants to merge 1 commit into
keptlive:mainfrom
chipitaps:fix-mcp-body-parser

Conversation

@chipitaps
Copy link
Copy Markdown

Problem

The HTTP transport for the MCP endpoint is unreachable from any external MCP client (Apify connectors, ChatGPT remote MCP, Cursor remote, etc.). Every JSON-RPC POST to https://mcp.vin/mcp returns:

{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error: Invalid JSON"},"id":null}

Reproducible with a plain curl:

curl -X POST https://mcp.vin/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'

Root cause

server.mjs mounts express.json() globally for parsing the REST API endpoints. By the time the request reaches the app.post('/mcp', ...) handler, req.body is already populated but the underlying request stream has been consumed.

The handler then calls:

await session.transport.handleRequest(req, res);

The MCP SDK's StreamableHTTPServerTransport.handleRequest falls back to reading the raw request stream when no parsedBody is supplied. The stream is empty, so the JSON parse fails and the transport emits a -32700 error.

Fix

Pass req.body as the third argument to handleRequest — exactly the path the SDK provides for this case (handleRequest(req, res, parsedBody?)). One-line change, no behaviour change for any other route.

Verification

Running locally before the change:

$ curl -sX POST localhost:3200/mcp -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{"jsonrpc":"2.0","id":1,"method":"initialize",...}'
{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error: Invalid JSON"},"id":null}

After the change:

event: message
data: {"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"vin-mcp","version":"1.2.0"}},"jsonrpc":"2.0","id":1}

initialize succeeds and subsequent tools/list + tools/call work end-to-end.

Why this matters

The README and homepage advertise https://mcp.vin/mcp as the remote HTTP endpoint for Claude Desktop / claude.ai / any MCP-compatible client. Right now the endpoint is unusable from anything other than the local stdio path — which means the hosted instance never gets used.

express.json() consumes the request body before the MCP transport reads it,
causing remote MCP clients (Apify, ChatGPT, Cursor remote, etc.) to fail with

  {"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error: Invalid JSON"},"id":null}

The MCP SDK's StreamableHTTPServerTransport.handleRequest accepts a third
`parsedBody` argument exactly for this case. Passing req.body in restores
remote MCP compatibility without changing the existing express-json behaviour
that the rest of the routes rely on.

Verified locally: `curl -X POST localhost:3200/mcp -d '{...initialize...}'`
now returns the expected handshake response.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant