Repository navigation
fix(mcp): Limit tool input elements and request body size - #1780
Conversation
Address GHSA-46jr-4wx8-v453 for the hosted MCP server. Upgrading the SDK alone does not apply the fix, because the new limits are opt-in. - Set maxToolInputElements (10,000) on the McpServer that buildServer creates for both SDK versions. Calls over the cap get an isError result before schema validation runs. - Cap /mcp POST bodies at 1 MiB in mcp-cloudflare. The agents createMcpHandler only forwards maxRequestBodySize to the SDK v2 lane. Its legacy classification and legacy compatibility lane keep the 4 MiB default, so the handler buffers the body itself and returns a JSON-RPC 413 when the body is too large. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| } | ||
|
|
||
| const server = new LegacyMcpServer(serverInfo); | ||
| const server = new LegacyMcpServer(serverInfo, serverOptions); |
There was a problem hiding this comment.
High severity vulnerability may affect your project—review required:
Line 178 lists a dependency (@modelcontextprotocol/server) with a known High severity vulnerability.
ℹ️ Why this matters
Affected versions of @modelcontextprotocol/sdk and @modelcontextprotocol/server are vulnerable to Allocation of Resources Without Limits or Throttling. McpServer places no limit on how many array elements and object keys it validates in tool-call arguments. A single unauthenticated HTTP request within the default 4 MiB body limit can exhaust the Node.js heap during validation and crash the server process (denial of service). After upgrading, also set the opt-in maxToolInputElements McpServer option and lower maxRequestBodySize.
References: GHSA
To resolve this comment:
Check if you expose the McpServer over an HTTP transport (Streamable HTTP, SSE, or createMcpHandler) rather than stdio only and it registers tools that accept input, especially arrays or records.
- If you're affected, upgrade this dependency to at least version 2.3.0 at pnpm-lock.yaml.
- If you're not affected, comment
/fp we don't use this [condition]
💬 Ignore this finding
To ignore this, reply with:
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
You can view more details on this finding in the Semgrep AppSec Platform here.
There was a problem hiding this comment.
This line is the mitigation, not a vulnerable use. The McpServer constructed here resolves @modelcontextprotocol/server 2.4.0 (or @modelcontextprotocol/sdk 1.32.2 for v1), which is patched, and it now sets maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS (10,000). Hosted /mcp bodies are also capped at 1 MiB in mcp-cloudflare. The only @modelcontextprotocol/server@2.0.0 lockfile entry (pnpm-lock.yaml L3012) is pinned exactly by @mastra/core, which only apps/docs-mcp uses. That is tracked separately in #1601. Leaving the Semgrep triage command to a maintainer.
|
|
||
| if (sdkVersion === "v2") { | ||
| const server = new ModernMcpServer(serverInfo); | ||
| const server = new ModernMcpServer(serverInfo, serverOptions); |
There was a problem hiding this comment.
High severity vulnerability may affect your project—review required:
Line 162 lists a dependency (@modelcontextprotocol/server) with a known High severity vulnerability.
ℹ️ Why this matters
Affected versions of @modelcontextprotocol/sdk and @modelcontextprotocol/server are vulnerable to Allocation of Resources Without Limits or Throttling. McpServer places no limit on how many array elements and object keys it validates in tool-call arguments. A single unauthenticated HTTP request within the default 4 MiB body limit can exhaust the Node.js heap during validation and crash the server process (denial of service). After upgrading, also set the opt-in maxToolInputElements McpServer option and lower maxRequestBodySize.
References: GHSA
To resolve this comment:
Check if you expose the McpServer over an HTTP transport (Streamable HTTP, SSE, or createMcpHandler) rather than stdio only and it registers tools that accept input, especially arrays or records.
- If you're affected, upgrade this dependency to at least version 2.3.0 at pnpm-lock.yaml.
- If you're not affected, comment
/fp we don't use this [condition]
💬 Ignore this finding
To ignore this, reply with:
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
You can view more details on this finding in the Semgrep AppSec Platform here.
There was a problem hiding this comment.
This line is the mitigation, not a vulnerable use. The McpServer constructed here resolves @modelcontextprotocol/server 2.4.0 (or @modelcontextprotocol/sdk 1.32.2 for v1), which is patched, and it now sets maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS (10,000). Hosted /mcp bodies are also capped at 1 MiB in mcp-cloudflare. The only @modelcontextprotocol/server@2.0.0 lockfile entry (pnpm-lock.yaml L3012) is pinned exactly by @mastra/core, which only apps/docs-mcp uses. That is tracked separately in #1601. Leaving the Semgrep triage command to a maintainer.
Conflicts: - packages/mcp-core/src/server.ts: keep #1780's maxToolInputElements on the single SDK v2 McpServer this branch builds. - packages/mcp-core/src/server.test.ts: keep both imports, and run #1780's input-cap test once, since buildServer no longer takes sdkVersion. - packages/mcp-server/package.json: keep the bundle package. #1778's test:package consumer check typechecks the old library exports, which the bundle no longer ships, so drop it and its test/package files. - package.json, AGENTS.md: keep both sides.
Fixes #1601. This is the Docs MCP follow-up to #1780, which did the same hardening for the hosted MCP server ([GHSA-46jr-4wx8-v453](GHSA-46jr-4wx8-v453)). Docs MCP already resolves `@modelcontextprotocol/sdk` 1.32.2 through the workspace override, so no SDK upgrade is needed. Both mitigations are opt-in, and this PR turns them on. It also moves the limits out of the hosted server so both servers share them. **Shared limits: `@sentry/toolkit-core/mcp-limits`.** The new module holds: - `MAX_MCP_REQUEST_BODY_BYTES` (1 MiB) and `MAX_TOOL_INPUT_ELEMENTS` (10,000), moved here from `mcp-cloudflare` and `mcp-core`. Their values and sizing rationale are unchanged from #1780. - `readBoundedBody`, which reads a body up to the cap. A declared `Content-Length` over the cap is rejected without reading, and streamed bytes are always counted, so bodies without `Content-Length` (or with a wrong one) are bounded too. - `boundMcpRequestBody` and `mcpPayloadTooLargeResponse`, which return the same JSON-RPC 413 shape the SDK uses. The hosted server (`mcp-core` and `mcp-cloudflare`) now imports these instead of keeping private copies. Its behavior doesn't change, and its existing body-limit and element-cap tests pass unchanged. **Docs MCP.** `createMcpServer` passes `maxToolInputElements`. `/mcp` and `/internal/docs` bound the body before any JSON parsing. That matters for `/mcp` because `@hono/mcp` parses the body itself, so the SDK's own transport limit wouldn't apply. The access check still runs first, so callers outside the service binding still get 403. Docs MCP inputs (at most a 2,000-character query plus 40 context strings, slugs, and a stack summary) fit well within the shared limits. Error contracts: - `/mcp`: 413 with the JSON-RPC error (`code: -32000`, `Payload Too Large: ...`). - `/internal/docs`: 413 with `{ "error": "Request body must not exceed 1048576 bytes" }`. Malformed JSON still gets the existing 400. - A tool call over the element cap gets the SDK's `isError` result before the handler runs, so no doctree fetch or model call happens. **Tests.** The body-limit tests live in toolkit-core: a body at the limit, oversized bodies with and without `Content-Length`, a body larger than its declared `Content-Length`, the replayed request, and the 413 shape. Docs MCP keeps one test per route to check that the route applies the limits. None of the tests call a model. I didn't add per-field `.max()` limits to `libs`/`features`. The element budget already bounds them, and a per-field cap would change the published tool schema. <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3Ac416b00d1a6e2a90d2762f20 --> -- [View Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Ac416b00d1a6e2a90d2762f20/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
Main's shared input limits (#1780, #1784) cover what this branch added for the hosted MCP, so they replace its MAX_MCP_* constants in mcp-core and the Code Mode request body limiter. The handler keeps main's boundMcpRequestBody() and only adds registerCodeModeTools(). Definitions are regenerated for submit_feedback_to_sentry_team. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017M6odW3kt5i65zATxfz9RX
Addresses GHSA-46jr-4wx8-v453 for the hosted MCP server. Semgrep flagged it on #1731. It goes in its own PR, as requested there.
The advisory:
McpServerdoes not limit how many array elements and object keys it validates intools/callarguments. One HTTP request under the default 4 MiB body limit can exhaust the heap during validation. We already resolve@modelcontextprotocol/server2.4.0 and@modelcontextprotocol/sdk1.32.2, which contain the patch. Upgrading alone changes nothing, though: both mitigations are opt-in, and this PR turns them on.What's exposed.
mcp-cloudflareserves mcp-core's SDK v2McpServeron/mcpthroughcreateMcpHandler. A request with aSentry-Bearerheader goes straight to the MCP server without the token being validated, so anyone can trigger tool-input validation. The only guard is the per-IP rate limit. Before this change, neither limit was set.Tool input elements: 10,000 (
MAX_TOOL_INPUT_ELEMENTSin mcp-core). The count is array elements plus object keys across the wholeargumentspayload. The largest bounded inputs today are about 100 elements (onboarding_status_updateID lists, withget_alert_optionsat 20). The unbounded ones, alert-rule triggers and action filters,fieldslists, andlink_issue.fields, stay in the low hundreds even for large real payloads. 10,000 matches the value the advisory suggests and leaves at least 50x headroom. A call over the cap gets anisErrorresult before schema validation or the tool handler runs. The cap applies to both SDK versions inbuildServer. stdio isn't affected by the advisory, but it costs nothing and keeps both transports consistent.Request body: 1 MiB (
MAX_MCP_REQUEST_BODY_BYTESin mcp-cloudflare, down from the SDK's 4 MiB default). The longest bounded text input is 4,096 characters (add_issue_note, feedback descriptions). Everything else is queries, slugs and IDs, so 1 MiB covers every supported request with wide headroom.One surprise:
createMcpHandlerfromagents0.32.0 forwardsmaxRequestBodySizeonly to the SDK v2 lane. Its legacy-request classification (isLegacyRequest) and its legacy compatibility lane still read up to 4 MiB. Today's clients mostly use the legacy lane. So setting the option alone leaves most traffic at 4 MiB. A test confirmed this: with only the option set, a body over 1 MiB got a 200. The handler now buffers the POST body itself, up to the cap, before handing it tocreateMcpHandler. It checks a declaredContent-Lengthand also counts streamed bytes. It returns the same JSON-RPC 413 shape as the SDK. The option is still passed, so the v2 lane uses the same limit.Tests (no model calls):
Content-Lengthgets the 413 payload. A streamed body over the limit withoutContent-Lengthgets a 413. Asearch_errorscall with 10,000fieldsentries is rejected with the element-cap error.Out of scope:
apps/docs-mcp. The flagged 2.0.0 lockfile entry comes in through@mastra/core, which pins@modelcontextprotocol/serverto exactly2.0.0. A pnpm override to 2.4.0 might work, but it's untested against Mastra, so it isn't in this PR. Docs MCP hardening is tracked in #1601.via @BYK.
--
View Session in Sentry