Skip to content

fix(mcp): Limit tool input elements and request body size - #1780

Merged
BYK merged 1 commit into
mainfrom
fix/mcp-tool-input-limits
Nov 1, 2026
Merged

BYK merged 1 commit into
mainfrom
fix/mcp-tool-input-limits

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

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: McpServer does not limit how many array elements and object keys it validates in tools/call arguments. One HTTP request under the default 4 MiB body limit can exhaust the heap during validation. We already resolve @modelcontextprotocol/server 2.4.0 and @modelcontextprotocol/sdk 1.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-cloudflare serves mcp-core's SDK v2 McpServer on /mcp through createMcpHandler. A request with a Sentry-Bearer header 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_ELEMENTS in mcp-core). The count is array elements plus object keys across the whole arguments payload. The largest bounded inputs today are about 100 elements (onboarding_status_update ID lists, with get_alert_options at 20). The unbounded ones, alert-rule triggers and action filters, fields lists, and link_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 an isError result before schema validation or the tool handler runs. The cap applies to both SDK versions in buildServer. stdio isn't affected by the advisory, but it costs nothing and keeps both transports consistent.

Request body: 1 MiB (MAX_MCP_REQUEST_BODY_BYTES in 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: createMcpHandler from agents 0.32.0 forwards maxRequestBodySize only 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 to createMcpHandler. It checks a declared Content-Length and 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):

  • mcp-core, both SDK versions: a call exactly at the element cap runs; one element more is rejected with a fixed message and the handler never runs.
  • mcp-cloudflare: a body just under 1 MiB works. A body over the limit with Content-Length gets the 413 payload. A streamed body over the limit without Content-Length gets a 413. A search_errors call with 10,000 fields entries 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/server to exactly 2.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

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>
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
sentry-local Ready Ready Preview Nov 1, 2026 4:38pm UTC

Request Review

}

const server = new LegacyMcpServer(serverInfo);
const server = new LegacyMcpServer(serverInfo, serverOptions);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@BYK
BYK merged commit 114c794 into main Nov 1, 2026
34 checks passed
@BYK
BYK deleted the fix/mcp-tool-input-limits branch November 1, 2026 16:47
sentry-junior Bot added a commit that referenced this pull request Nov 1, 2026
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.
BYK added a commit that referenced this pull request Nov 1, 2026
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>
betegon pushed a commit that referenced this pull request Nov 2, 2026
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

This branch was successfully deployed

1 active deployment
Preview — c529a917 Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant