Skip to content

Commit 17194cd

Browse files
milliyinclaude
andcommitted
fix: MCP endpoint response handling — force Accept header, convert Buffer to string
Three fixes: - Force Accept: application/json, text/event-stream on shim to satisfy MCP SDK - Convert Buffer to string in captureResponse to avoid raw byte serialization on Vercel - Remove delegation to dummy socket write/end (was causing silent errors) - Add JSON parse error handling for malformed request bodies Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9d7ce8a commit 17194cd

1 file changed

Lines changed: 28 additions & 18 deletions

File tree

‎src/app/api/v1/mcp/route.ts‎

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,55 +18,55 @@ function toIncomingMessage(request: Request, body: string): IncomingMessage {
1818
request.headers.forEach((value, key) => {
1919
req.headers[key.toLowerCase()] = value;
2020
});
21+
// MCP Streamable HTTP requires Accept to include both types
22+
req.headers["accept"] = "application/json, text/event-stream";
2123
// Push body data and signal end
2224
req.push(body);
2325
req.push(null);
2426
return req;
2527
}
2628

2729
/**
28-
* Capture a ServerResponse into a Web Response
30+
* Capture a ServerResponse into a Web Response.
31+
* Does NOT delegate to the real write/end (the underlying socket is a dummy),
32+
* instead captures all chunks and resolves a Web Response promise.
2933
*/
3034
function captureResponse(): { res: ServerResponse; getResponse: () => Promise<Response> } {
3135
const socket = new Socket();
3236
const res = new ServerResponse(new IncomingMessage(socket));
3337

3438
const chunks: Buffer[] = [];
35-
let statusCode = 200;
3639
const headers: Record<string, string> = {};
3740
let resolvePromise: (r: Response) => void;
3841

3942
const responsePromise = new Promise<Response>((resolve) => {
4043
resolvePromise = resolve;
4144
});
4245

43-
// Intercept writes
44-
const origWrite = res.write.bind(res);
45-
const origEnd = res.end.bind(res);
46-
47-
res.write = function (chunk: unknown, ...args: unknown[]) {
46+
// Capture writes without delegating to the dummy socket
47+
res.write = function (chunk: unknown): boolean {
4848
if (chunk) {
4949
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
5050
}
51-
return origWrite(chunk, ...args as [BufferEncoding, (() => void)?]);
52-
};
51+
return true;
52+
} as typeof res.write;
5353

54-
res.end = function (chunk?: unknown, ...args: unknown[]) {
55-
if (chunk) {
54+
res.end = function (chunk?: unknown): ServerResponse {
55+
if (chunk && typeof chunk !== "function") {
5656
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
5757
}
58-
statusCode = res.statusCode;
59-
// Collect headers
58+
// Collect headers set by the transport
6059
const rawHeaders = res.getHeaders();
6160
for (const [key, val] of Object.entries(rawHeaders)) {
6261
if (val !== undefined) headers[key] = String(val);
6362
}
64-
resolvePromise!(new Response(Buffer.concat(chunks), {
65-
status: statusCode,
63+
// Convert Buffer to string — MCP responses are JSON or SSE text
64+
resolvePromise!(new Response(Buffer.concat(chunks).toString("utf-8"), {
65+
status: res.statusCode,
6666
headers,
6767
}));
68-
return origEnd(chunk, ...args as [BufferEncoding, (() => void)?]);
69-
};
68+
return res;
69+
} as typeof res.end;
7070

7171
return { res, getResponse: () => responsePromise };
7272
}
@@ -84,7 +84,17 @@ export async function POST(request: Request) {
8484
}
8585

8686
const body = await request.text();
87-
const parsedBody = JSON.parse(body);
87+
88+
let parsedBody: unknown;
89+
try {
90+
parsedBody = JSON.parse(body);
91+
} catch {
92+
return Response.json({
93+
jsonrpc: "2.0",
94+
error: { code: -32700, message: "Parse error: invalid JSON" },
95+
id: null,
96+
}, { status: 400 });
97+
}
8898

8999
const server = createMcpServer(token);
90100
const transport = new StreamableHTTPServerTransport({

0 commit comments

Comments
 (0)