amplitude / amplitude/mcp-server-guide
MCP server returns truncated JSON-RPC error body (82 bytes, invalid JSON) + HTTP/2 stream never terminated
- Lenguaje dominante
- Sin datos de lenguaje
- Estrellas
- 45
- Forks
- 5
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Descripción
# Bug: MCP server returns truncated JSON-RPC error body (82 bytes, invalid JSON)
## Summary
Every request to `https://mcp.amplitude.com/mcp` with a valid OAuth token returns HTTP 500 with a truncated, invalid JSON body. The server sends exactly 82 bytes regardless of request type, cutting the JSON off mid-string.
## Steps to Reproduce
### Step 1 — Obtain a valid access token via OAuth (curl + browser)
**1a. Register a client dynamically:**
```bash
curl -s -X POST https://mcp.amplitude.com/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "debug-client",
"redirect_uris": ["http://localhost:9999/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}'
```
Save the returned `client_id`.
**1b. Generate a PKCE pair and capture into shell variables:**
```bash
eval $(node -e "
const crypto = require('crypto');
const v = crypto.randomBytes(32).toString('base64url');
const c = crypto.createHash('sha256').update(v).digest('base64url');
console.log('CODE_VERIFIER=' + v);
console.log('CODE_CHALLENGE=' + c);
")
```
**1c. Start a temporary listener on port 9999** to capture the auth code from the redirect (the browser redirects to `localhost:9999` after authorization — without a listener the code may not be visible in the address bar, especially on Safari/Windows):
```bash
# macOS / Linux (run in a separate terminal)
nc -l 9999
# Windows (PowerShell, run in a separate terminal)
$l = [System.Net.Sockets.TcpListener]::new(9999); $l.Start(); $c = $l.AcceptTcpClient(); [System.IO.StreamReader]::new($c.GetStream()).ReadLine(); $l.Stop()
```
**1d. Open this URL in a browser** (replace ``):
```
https://mcp.amplitude.com/authorize?client_id=&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A9999%2Fcallback&scope=mcp%3Aread+mcp%3Awrite+offline_access&state=test&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256
```
After authorizing, the `nc` listener prints the raw HTTP request — copy `` from the `GET /callback?code=&state=test` line.
**1e. Exchange the code for a token** (replace `` and ``):
```bash
TOKEN=$(curl -s -X POST https://mcp.amplitude.com/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=&redirect_uri=http%3A%2F%2Flocalhost%3A9999%2Fcallback&client_id=&code_verifier=$CODE_VERIFIER" \
| node -e "const d=require('fs').readFileSync('/dev/stdin','utf8'); console.log(JSON.parse(d).access_token)")
```
### Step 2 — Reproduce the bug
**Symptom A — HTTP/2 stream never terminated (connection hangs):**
```bash
curl -v --max-time 10 -X POST https://mcp.amplitude.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
```
Observe: response headers arrive instantly (`HTTP/2 500`, `content-length: 82`) but curl hangs until `--max-time` kills it — no body is printed. This is because the server writes 82 bytes but never sends the HTTP/2 `END_STREAM` frame to close the response, so curl waits indefinitely.
**Symptom B — Truncated, invalid JSON body:**
The MCP TypeScript SDK reads exactly `Content-Length` bytes and closes, exposing the actual body:
```bash
npm install @modelcontextprotocol/sdk
```
```js
// debug.mjs
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.amplitude.com/mcp"),
{ requestInit: { headers: { Authorization: "Bearer " } } }
);
const client = new Client({ name: "test", version: "1.0" }, { capabilities: {} });
await client.connect(transport); // throws with the body below
```
```bash
node debug.mjs
```
## Actual Response
**HTTP status:** `500`
**Content-Length:** `82`
**Body (all 82 bytes, confirmed via `body.length` in Node — truncated mid-JSON):**
```
{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal server error [requestI
```
The JSON is invalid — the string value, object, and array are all unclosed. The body is cut off at exactly 82 bytes, 31 bytes into the error message.
## Expected Response
A complete, valid JSON-RPC error response, e.g.:
```json
{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal server error [requestId: req_xxx]"},"id":1}
```
## Analysis
The 82-byte body breaks down as:
- 51 bytes: fixed envelope `{"jsonrpc":"2.0","error":{"code":-32603,"message":"`
- 31 bytes: start of message `Internal server error [requestI`
The body is cut off mid-string with the JSON never closed. The server appears to abort or crash mid-response after writing this partial content. Both the truncated body and the missing HTTP/2 `END_STREAM` frame are symptoms of the same failure.
Verified: an invalid token returns a fast HTTP 401 in ~0.7s. Only valid tokens trigger the 500 + partial body, confirming the failure occurs after authentication succeeds.
## Environment
- Endpoint: `https://mcp.amplitude.com/mcp`
- MCP SDK: `@modelcontextprotocol/sdk` latest
- Transport: Streamable HTTP
- Protocol version: `2024-11-05`
- Affected requests: `initialize` confirmed; GET `/mcp` also returns the same 500 + partial body
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Evaluación
Este issue todavía no se ha evaluado.