fix(mcp): upgrade to MCP SDK 2.3.0 and raise the per-event SSE size cap by joshua-berri · Pull Request #47034 · BerriAI/litellm · GitHub
Skip to content

fix(mcp): upgrade to MCP SDK 2.3.0 and raise the per-event SSE size cap - #47034

Merged
joshua-berri merged 4 commits into
mainfrom
litellm_mcp_sdk_2_3_sse_event_size
Oct 20, 2026
Merged

joshua-berri merged 4 commits into
mainfrom
litellm_mcp_sdk_2_3_sse_event_size

Conversation

@joshua-berri

@joshua-berri joshua-berri commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Large MCP SSE tool results fail through the gateway
  • Invalid size limits break discovery and tool calls

How it solves it:

  • Upgrade MCP to 2.3.0 and pass a 64 MiB limit
  • Validate overrides using the existing range helper
  • Warn and fall back for invalid byte limits

User Flow

Before, a large tool result fails even though the upstream answered

  1. Register a Streamable HTTP MCP server exposing dump(size)
  2. POST http://localhost:4000/mcp-rest/tools/call with {"server_id":"big","name":"dump","arguments":{"size":3145728}}
  3. Observe isError: true and MCPError: SSE stream ended without a response

After, the caller receives the complete tool result

  1. Register the same Streamable HTTP MCP server
  2. POST the same request to http://localhost:4000/mcp-rest/tools/call
  3. Observe isError: false and exactly 3,145,728 characters

Affected release

Regression in v1.103.0 following the MCP SDK 2.x migration in #41718

Linear ticket

Resolves LIT-9750

Implementation and validation

MCP 2.3.0 exposes max_sse_event_size, but its default remains 1 MiB. The gateway passes 64 MiB by default and supports LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES. Positive platform-sized integers are preserved. Unset, blank, nonnumeric, nonpositive and oversized integer values use the default, with the existing parser logging invalid values

The proxy and MCP extras require mcp>=2.3.0,<3 and httpx2>=2.10.0,<3. The uv override handles the admin MCP development dependency's older exact MCP pin. No unrelated dependency versions change

Affected constants and MCP client tests pass, 555 total, on the identical production code at 916ebf815bed27d17a2193b84f67a48a1be11db2. The follow-up 86108ee8a9489c7d10de5226bc98fcb5ab76c23d adds only an integration test. Final commit fb3c00bdc5574d3ee48c807979b5252c194f1389 removes the flagged dependency comment without changing code or locked dependencies. The final nine-case regression also passes separately. Against the preceding PR tip, the same regression fails for zero, negative and oversized limits, with the six supported controls passing

Canonical make check and uv lock --check pass against merge base aa32e4cc8861eb6134391949373ebae6049311ff. Local coverage covers the changed executable line and all 16 lines and six branches in the transport factory. The completed current-tip Codecov report is available, with 100% patch coverage (2/2 lines) and 59.28% repository-wide coverage. The patch number is separate from the local executable-line and branch measurements. Eight unrelated Pydantic ReadOnly warnings come from unchanged definitions

The configured-limit integration test starts an owned real proxy with a 4096-byte limit, discovers its tools, accepts a 512-character result, rejects an 8192-character result, and accepts the next 512-character result. It passes locally. Replacing the transport argument with a hardcoded 64 MiB makes it fail at the rejection assertion. This addresses Greptile’s configured-limit coverage finding. The integration suite runs on main in CircleCI, so this new case was executed locally before merge

Screenshots / Proof of Fix

Use source installations with each revision's locked dependencies. Start an SDK MCPServer exposing dump(size) as return "x" * size at http://127.0.0.1:18901/mcp. Register it as big with transport: http. Start the source proxy on port 4100 with a local master key in $KEY. This server includes the text in both content and structuredContent, so the SSE event is approximately twice the requested size

curl -sS 'http://127.0.0.1:4100/mcp-rest/tools/list?server_id=big' \
  -H "Authorization: Bearer $KEY"
curl -sS http://127.0.0.1:4100/mcp-rest/tools/call \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"server_id":"big","name":"dump","arguments":{"size":3145728}}' |
  python3 -c 'import json,sys; b=json.load(sys.stdin); t=b["content"][0]["text"]; print(b["isError"], len(t), t == "x" * 3145728)'

Before (aa32e4c)

Large result

  1. Run the discovery command and observe the dump tool
  2. Run the tool call and observe isError: true, MCPError: SSE stream ended without a response
  3. Repeat with size 500000 and observe the complete successful result

After (fb3c00b)

Large result

  1. Run the same discovery command and observe the dump tool
  2. Run the same tool call and observe False 3145728 True
  3. Repeat with size 500000 and observe the complete successful result

Invalid-limit repair and supported controls

Independent verification repeated the merge-base and final-commit cases on separate source builds with their locked MCP versions (2.2.0 on base, 2.3.0 on final). Both integration regressions fail on base and pass on final. The following fresh local replay compares preceding PR tip 828c770857b294c2a64c99fef0b3853fd08db202 with final commit fb3c00bdc5574d3ee48c807979b5252c194f1389, using MCP 2.3.0 and httpx2 2.13.0 on both builds

  1. Restart each proxy with LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES=0, then -1. Run the same discovery and call commands for sizes 500000 and 3145728. Before, discovery and calls return HTTP 500 for both SSE and JSON-response MCP servers. After, discovery returns dump and every call returns HTTP 200 with the exact requested text
  2. Restart with limit 200000 and request sizes 90000, 110000 and 90000 in order. On both builds, SSE returns the exact first result, rejects the second with Server-sent event exceeded the 200000 byte limit, then returns the exact third result. JSON-response calls all succeed
  3. Repeat the large-result calls with the setting unset and with invalid. Both builds return exact results using the default limit

All 64 HTTP observations matched the expected outcomes. These are real source proxies and SDK MCP servers, with no mocked gateway calls. All local verification processes were stopped

Final verification

Reviewed commit fb3c00bdc5574d3ee48c807979b5252c194f1389

Greptile gives 5/5 and confirms both findings resolved. Bugbot reports no issues. Veria reports no security issues. LiteReview gives 5/5 with no correctness findings, 618 tests passing, and a passing configured-limit integration test that fails when the transport argument is removed. All review threads are resolved

Independent source-build verification compared merge base aa32e4cc8861eb6134391949373ebae6049311ff, prior PR tip 828c770857b294c2a64c99fef0b3853fd08db202, and the final commit above. It repeated the curl flows above and ran both integration nodes through the repository harness

INTEGRATION_WORKERS=1 .venv/bin/python tests/integration/run.py mcp tests/integration/mcp/test_mcp_transports.py::test_tool_result_larger_than_one_mib_in_a_single_sse_event_reaches_the_caller
INTEGRATION_WORKERS=1 .venv/bin/python tests/integration/run.py mcp tests/integration/mcp/test_mcp_transports.py::test_configured_sse_limit_rejects_large_results_and_allows_the_next_call
Base 3 MiB SSE call: isError true, MCPError: SSE stream ended without a response
Final 3 MiB SSE and JSON calls: exact result, isError false
Prior env 0 / -1: discovery and calls HTTP 500
Final env 0 / -1: discovery HTTP 200, exact results, warning and default fallback
Final env 200000: 90000 exact, 110000 rejected, 90000 exact again
Constants regression: prior 3 failed / 6 passed, final 9 passed
Large SSE integration: base failed, final passed
Configured-limit integration: base failed because the large result was not rejected, final passed

The baseline integration runs applied only the final test diff. Original zero/negative proxy logs did not drain before shutdown, so warning text was recaptured with separate startup-only proxies on the same final source. Response observations were retained from the original processes. Task-owned verifier processes and environments were removed

Pre-Submission checklist

  • Meaningful regression tests pass locally
  • Applicable local lint and type checks pass
  • Scope remains the MCP SSE failure
  • Required current-tip CI and completed Codecov report pass
  • Fresh independent verification and all required bot reviews complete

Type

Bug Fix
Test

Caveats

Medium

  • Legacy SSE transport retains its SDK behavior
  • Downstream MCP clients can still enforce their own smaller limits

Low

  • All required CI and optional packaging/image checks pass
  • OSV scan fails on unchanged dependency entries, classified unrelated to this PR
  • The contributor confirms the agreement is already signed. GitHub does not publish a separate CLA result
  • Environment-variable documentation is tracked separately

Final Attestation

  • All required current-tip verification is complete

MCP SDK 2.x reads Streamable HTTP responses through httpx2's SSE parser,
which rejects any single event over 1 MiB, so a large tools/call result
failed with 'SSE stream ended without a response'. mcp 2.3.0 exposes
max_sse_event_size, and MCPClient now passes a 64 MiB default,
configurable with LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

@codecov

codecov Bot commented Oct 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 32 untouched benchmarks


Comparing litellm_mcp_sdk_2_3_sse_event_size (fb3c00b) with main (6bc1b2b)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (27efc38) during the generation of this report, so 6bc1b2b was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@greptileai @litereview code-review Please review commit 828c770 for SDK compatibility, SSE limit handling, and meaningful regression coverage before merge

@joshua-berri

Copy link
Copy Markdown
Contributor Author

bugbot run Please review commit 828c770 for SDK compatibility, SSE size enforcement, and regression coverage before merge

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@litereview code-review Please verify the current SDK upgrade, SSE regression test, and compatibility evidence for commit 828c770

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review the current SDK upgrade, SSE regression test, and compatibility evidence for commit 828c770 before merge

@litereview

litereview Bot commented Oct 18, 2026

Copy link
Copy Markdown

Review already requested

code-review is already reviewing 828c770857b2. View existing review.

@litereview

litereview Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

⚠️ code-review

5/5 · Ready for human review

Scope: full · Full PR review.

Completed · Some checks not run.

No correctness findings.

Checks and execution details
  • Passed · Real testing evidence (required): Not applicable: no LLM translation changes in the scoped paths. The PR changes litellm/constants.py, litellm/experimental_mcp_client/client.py, pyproject.toml, uv.lock and tests. It touches nothing under litellm/llms/** or litellm/litellm_core_utils/prompt_templates/**.

  • Passed · Compatibility requirements (required): No supported behavior breaks.

  • Default SSE limit: an unset LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES raises the per-event cap from the SDK's 1 MiB to 64 MiB. Requests that succeeded before still succeed, and SSE tool results over 1 MiB that used to fail now succeed.

  • New environment variable: the variable is additive. Invalid values fall back to the default through get_env_int_in_range.

  • Dependency floors: the proxy/mcp extras now require mcp>=2.3.0 and httpx2>=2.10.0. mcp 2.3.0 itself requires httpx2>=2.10.0.

  • uv override: mcp>=2.3.0,&lt;3 overrides litellm-admin-mcp's exact mcp==2.2.0 pin. The installed admin-mcp modules still import, and tests/unit/proxy/test_admin_mcp.py passed.

No ! marker is required.

  • Passed · Changed behavior and result consistency (required): I read the root AGENTS.md and tests/integration/AGENTS.md/README.md.

Traced path: the env var flows through get_env_int_in_range(..., 1, sys.maxsize) into MCP_MAX_SSE_EVENT_SIZE_BYTES, then into _create_transport_context. That function passes it to streamable_http_client(max_sse_event_size=...). In the installed mcp 2.3.0, StreamableHTTPTransport sends the value to EventSource(max_event_size=...) on the POST, GET and resume paths. The SDK rejects values <= 0. The range floor of 1 prevents those values from reaching it.

Boundary cases: unset, blank, nonnumeric, 0, -1, 1, sys.maxsize and sys.maxsize+1 are all covered by the parametrized tests/unit/test_constants.py test. The legacy sse_client path is unchanged, and the PR documents that as a caveat.

Executed:

  • pytest tests/unit/test_constants.py tests/unit/experimental_mcp_client/test_mcp_client.py tests/unit/proxy/test_admin_mcp.py -n 8: 618 passed.
  • The real-proxy integration test test_configured_sse_limit_rejects_large_results_and_allows_the_next_call passed locally against this head.
  • With the max_sse_event_size= argument removed, the same test failed at the isError assertion for large. The test is sensitive to the defect.

No unresolved defects remain to reconcile.

  • Not run · 3 MiB single-event integration test via shared gateway fixture (optional): I did not run test_tool_result_larger_than_one_mib_in_a_single_sse_event_reaches_the_caller because it needs the full CircleCI integration harness (shared gateway, Postgres, Redis). The configured-limit test exercises the same transport argument through a real owned proxy.

View review · Reviewed fb3c00bdc557

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@greptileai @litereview code-review Please review 916ebf8, including invalid SSE limits, supported positive overrides, regression coverage, and the updated live evidence

@joshua-berri

Copy link
Copy Markdown
Contributor Author

bugbot run Please review 916ebf8 for invalid SSE limit handling, supported overrides, regression coverage, and compatibility with existing MCP transports

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@litereview code-review Please review commit 916ebf8 for invalid SSE limits, transport compatibility, regression tests, and current live verification evidence

@litereview

litereview Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review already requested

code-review is already reviewing 916ebf815bed. View existing review.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] Both previous findings are addressed, with no remaining review findings blocking merge

Summary

Both previous findings are addressed. No new issues found

  • Streamable HTTP MCP calls can receive larger tool results.

Reviews (3) · Last reviewed commit: "chore(mcp): remove redundant dependency ..." · Reviewed by Greptile

Comment thread tests/integration/mcp/test_mcp_transports.py
@joshua-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 86108ee and confirm the configured-limit finding is resolved by the real proxy regression and hardcoded-limit mutation evidence

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@litereview code-review Please review 86108ee, including configured SSE limit rejection and recovery, mutation evidence, and refreshed live verification results

@joshua-berri

Copy link
Copy Markdown
Contributor Author

bugbot run Please review 86108ee, including configured SSE limit rejection and recovery, mutation evidence, and refreshed live verification results

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread pyproject.toml Outdated
@joshua-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review fb3c00b and confirm both findings resolved. The latest commit only removes the flagged dependency comment

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@litereview code-review Please review final commit fb3c00b, covering validated limits, configured-limit transport regression, mutation evidence, and the dependency comment removal

@joshua-berri

Copy link
Copy Markdown
Contributor Author

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

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 fb3c00b. Configure here.

@joshua-berri
joshua-berri merged commit 4aec80f into main Oct 20, 2026
113 of 116 checks passed
@joshua-berri
joshua-berri deleted the litellm_mcp_sdk_2_3_sse_event_size branch October 20, 2026 23:00
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.

2 participants