Skip to content

fix(mcp): cap transport response bodies to bound a hostile tools/call payload#5850

Merged
waleedlatif1 merged 5 commits into
stagingfrom
fix/mcp-transport-response-cap
Jul 22, 2026
Merged

fix(mcp): cap transport response bodies to bound a hostile tools/call payload#5850
waleedlatif1 merged 5 commits into
stagingfrom
fix/mcp-transport-response-cap

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Closes the one gap a full-lifecycle comparison against OpenCode / LibreChat / Claude Code / VS Code surfaced — the single place a reference client (LibreChat) was stricter than us.

  • The live MCP transport had no response-body byte cap, so a hostile server could stream an unbounded tools/call result and OOM the process. We already cap discovery (5 MiB) and OAuth (1 MiB) bodies, but not arbitrary tool-result bodies.
  • Now caps non-GET response bodies at 16 MiB (matching LibreChat's transport cap), counted as they stream (not buffered — an oversized body aborts the SDK's read instead of accumulating in memory).
  • Deliberately leaves the standalone GET SSE notification stream uncapped — it's long-lived by design, so a cumulative byte cap would eventually break legitimate sessions. The cap targets exactly the request/response exchanges (tools/call, initialize) where a bounded body is correct.

With this, every one of the 14 MCP lifecycle stages is at or above state-of-the-art reference-client practice.

Type of Change

  • Bug fix (security hardening)

Testing

  • New pinned-fetch test: an oversized POST body errors on read; the GET SSE stream streams through unbounded. 43 tests green across the pinned-fetch + client suites; tsc + biome clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

… payload

A full-lifecycle comparison against LibreChat found the one place a reference
client was stricter: the live transport had no response-body byte cap, so a
hostile server could stream an unbounded tools/call result and OOM the process
(discovery and OAuth bodies were already capped). Cap non-GET response bodies at
16 MiB — counted as they stream, not buffered — while leaving the standalone GET
SSE notification stream uncapped (a cumulative cap would break its long-lived
nature). Mirrors LibreChat's transport response-size cap.
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 22, 2026 5:43pm

Request Review

@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Security hardening on the live MCP transport path; behavior change for tool results over 16 MiB and subtle response wrapping could affect edge cases, but GET SSE and normal-sized payloads are unchanged.

Overview
Adds a 16 MiB streaming cap on MCP transport non-GET response bodies (tools/call, initialize, etc.) so a hostile server cannot OOM the process with an unbounded stream. The limit is enforced via a TransformStream that counts bytes as they flow—no full buffering—and throws McpError when exceeded.

GET requests (the long-lived standalone SSE notification stream) stay uncapped so legitimate sessions are not cut off. The same wrapping applies to createGuardedMcpFetch and the self-hosted createPinnedPrivateMcpFetch path. Wrapped responses preserve url and redirected for SDK OAuth metadata resolution, and strip content-encoding / content-length on the wrapped body.

Tests cover oversized POST vs uncapped GET, and metadata fields on capped responses.

Reviewed by Cursor Bugbot for commit b4cd142. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR limits MCP transport response bodies to reduce memory exhaustion risk. The main changes are:

  • Adds a 16 MiB streaming limit for non-GET MCP responses.
  • Leaves long-lived GET SSE notification streams unlimited.
  • Preserves response URL and redirect metadata after wrapping.
  • Adds tests for oversized POST responses and GET streaming.

Confidence Score: 5/5

This looks safe to merge.

  • The updated transport limits non-GET response bodies while they stream.
  • The long-lived GET notification stream remains unaffected.
  • Tests cover the main limit and metadata behavior.
  • No blocking issues were found in the changed code.

Important Files Changed

Filename Overview
apps/sim/lib/mcp/pinned-fetch.ts Adds streaming response-size enforcement for non-GET MCP transport exchanges while preserving GET SSE behavior and response metadata.
apps/sim/lib/mcp/pinned-fetch.test.ts Adds coverage for oversized POST rejection, unlimited GET streaming, dispatcher configuration, and redirect metadata.

Reviews (4): Last reviewed commit: "fix(mcp): drop stale framing headers on ..." | Re-trigger Greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/mcp/pinned-fetch.ts
new Response() resets url/redirected; the SDK resolves relative auth-metadata
URLs (resource_metadata) against response.url, so carry the originals over via
defineProperty on the wrapped response.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 554376d. Configure here.

The wrapped body is the already-decoded stream, so content-encoding/content-length
would misdescribe it — strip them, matching bufferUnderDeadline.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b4cd142. Configure here.

@waleedlatif1
waleedlatif1 merged commit e841e4e into staging Jul 22, 2026
20 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/mcp-transport-response-cap branch July 22, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant