diff --git a/README.md b/README.md index 876a096..98627ee 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ configured, so the server is demoable without a live store. MCP over **Streamable HTTP**: clients POST JSON-RPC 2.0 to `/mcp`. Implements `initialize`, `tools/list`, `tools/call`, and `ping`. `tools/list` only advertises -the tools the calling client is scoped for. +the tools the calling client is scoped for. Non-object JSON values receive a +JSON-RPC `-32600` Invalid Request response before dispatch. ## Auth & scopes diff --git a/src/index.ts b/src/index.ts index 7b4cca9..8fcf0cc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,14 +51,18 @@ export default { return aiChat(req, env); } - let body: RpcRequest; + let body: unknown; try { - body = (await req.json()) as RpcRequest; + body = await req.json(); } catch { return json(rpcError(null, -32700, "parse error")); } - const result = await handle(body, env, caller); + if (body === null || typeof body !== "object" || Array.isArray(body)) { + return json(rpcError(null, -32600, "invalid request")); + } + + const result = await handle(body as RpcRequest, env, caller); // Notifications (no id) get a 202 with no body per JSON-RPC. if (result === undefined) return new Response(null, { status: 202 }); return json(result); diff --git a/test/index.test.mjs b/test/index.test.mjs index ef49ed2..cae75b5 100644 --- a/test/index.test.mjs +++ b/test/index.test.mjs @@ -69,6 +69,25 @@ function mcpRequest(env, body) { ); } +test("rejects non-object JSON-RPC request bodies", async (t) => { + const env = { + BRIDGEKIT_CLIENTS: JSON.stringify({ + "client-key": { name: "reader", tools: [], allowWrite: false }, + }), + }; + + for (const body of [null, []]) { + await t.test(JSON.stringify(body), async () => { + const response = await mcpRequest(env, body); + assert.deepEqual(await response.json(), { + jsonrpc: "2.0", + id: null, + error: { code: -32600, message: "invalid request" }, + }); + }); + } +}); + test("tools/call rejects non-object arguments", async (t) => { let upstreamCalls = 0; globalThis.fetch = async () => {