Skip to content

fix(mcp): accept integer progressToken in host progress capture - #32402

Merged
tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_int_progress_token
Jul 8, 2026
Merged

fix(mcp): accept integer progressToken in host progress capture#32402
tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_int_progress_token

Conversation

@tin-berri

@tin-berri tin-berri commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Fixes #32181

Linear ticket

Resolves LIT-4256

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests); 126 of 128 pass, the two reds are CodSpeed noise (a 25% "regression" on an inference benchmark this MCP debug-path change never executes, next to 32-48% "improvements" on equally untouched benchmarks) and an llm_responses_api_testing provider flake that passes on sibling PRs and needs a CircleCI rerun
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Screenshots / Proof of Fix

The MCP spec types progressToken as string or integer, and the MCP Python SDK matches (ProgressToken = str | int in mcp/types.py). Clients such as Claude Code send monotonically increasing integer tokens by default. _capture_host_progress_callback slices the raw token in an eagerly evaluated debug f-string (host_token[:8]), so any tools/call carrying an integer token raises TypeError: 'int' object is not subscriptable before the backend MCP server is ever dispatched; the handler's catch-all converts that into an isError tool result, so the backend logs stay clean and the failure masquerades as a gateway fault. The same function also drops the spec-valid integer token 0 through its truthiness guard, silently disabling progress forwarding for it

Live proxy setup, identical for the before and after runs: a real public MCP server behind the proxy, streamable HTTP protocol path (which is the path that captures the host progress context), real Postgres

general_settings:
  master_key: sk-1234

mcp_servers:
  deepwiki:
    url: https://mcp.deepwiki.com/mcp
    transport: http
python litellm/proxy/proxy_cli.py --config litellm/proxy/gh32181_config.yaml --detailed_debug --use_v2_migration_resolver --port 4181

MCP handshake used by both runs (initialize, capture the session id, send the initialized notification):

SID=$(curl -s -D - -o /dev/null -X POST http://localhost:4181/mcp/ \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl-repro","version":"0.0.1"}}}' \
  | awk 'BEGIN{IGNORECASE=1}/^mcp-session-id:/{print $2}' | tr -d '\r')

curl -s -o /dev/null -w "initialized:%{http_code}\n" -X POST http://localhost:4181/mcp/ \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

Before (bug present, captured at db2402754a, the unmodified litellm_internal_staging tip)

A tools/call with an integer progressToken fails before the backend is dispatched

curl -s -X POST http://localhost:4181/mcp/ \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"deepwiki-read_wiki_structure","arguments":{"repoName":"BerriAI/litellm"},"_meta":{"progressToken":1}}}'
event: message
data: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"Error: 'int' object is not subscriptable"}],"isError":true}}

Proxy log at the same moment, showing the failure is the debug f-string in _capture_host_progress_callback and that it fires before call_mcp_tool

17:07:30 - LiteLLM:ERROR: server.py:981 - MCP mcp_server_tool_call - error: 'int' object is not subscriptable
    host_progress_callback = _capture_host_progress_callback(server)
  File ".../litellm/proxy/_experimental/mcp_server/server.py", line 735, in _capture_host_progress_callback
    verbose_logger.debug(f"Host progressToken captured: {host_token[:8]}...")
TypeError: 'int' object is not subscriptable

Control call on the same unfixed proxy with a string token succeeds, confirming the failure is integer-specific

curl -s -X POST http://localhost:4181/mcp/ \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"deepwiki-read_wiki_structure","arguments":{"repoName":"BerriAI/litellm"},"_meta":{"progressToken":"tok-1"}}}'
event: message
data: {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"Available pages for BerriAI/litellm:\n\n- 1 Overview\n  - 1.1 Getting Started and Repo Orientation\n ...

After (fix applied, captured at 75755de091)

The same call now returns the real tool result, and the edge cases hold: integer token 0 (previously silently dropped by the truthiness guard), a string token, and no token at all

for TOK in '"_meta":{"progressToken":1},' '"_meta":{"progressToken":0},' '"_meta":{"progressToken":"tok-1"},' ''; do
  curl -s -X POST http://localhost:4181/mcp/ \
    -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
    -d "{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{${TOK}\"name\":\"deepwiki-read_wiki_structure\",\"arguments\":{\"repoName\":\"BerriAI/litellm\"}}}"
done

All four variants return the tool output

data: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"Available pages for BerriAI/litellm:\n\n- 1 Overview\n  - 1.1 Getting Started and Repo Orientation\n ...

Proxy log confirming integer tokens (including 0) now make it through capture

17:12:33 - LiteLLM:DEBUG: server.py:735 - Host progressToken captured: 1...
17:12:34 - LiteLLM:DEBUG: server.py:735 - Host progressToken captured: 0...

Type

🐛 Bug Fix

Changes

_capture_host_progress_callback in litellm/proxy/_experimental/mcp_server/server.py now stringifies the token only inside the debug log line (str(host_token)[:8]), keeping the original value for send_progress_notification so the host gets back exactly the token it sent, and the guard checks host_token is None instead of truthiness so the spec-valid integer token 0 no longer disables progress forwarding

Three regression tests added to TestCaptureHostProgressCallback in tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_tool_search.py: an integer token returns a callable (raised TypeError before the fix), integer token 0 returns a callable (returned None before the fix), and the forwarded token preserves the original integer value and type. All three fail on the unfixed code and pass with the fix

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a TypeError in _capture_host_progress_callback that prevented MCP tools/call requests from completing when the _meta.progressToken was an integer (as sent by Claude Code and permitted by the MCP spec). It also fixes a silent no-op for the valid integer token 0.

  • server.py: The debug log now calls str(host_token)[:8] instead of slicing the raw token directly, and the early-return guard changes from a truthiness check to an explicit is None check so integer 0 is no longer treated as absent.
  • test_mcp_tool_search.py: Three new regression tests cover integer tokens, the zero edge case, and end-to-end value/type preservation through send_progress_notification.

Confidence Score: 5/5

Safe to merge — the change is a two-line targeted fix with no side effects on other paths.

Both changes are minimal and correct: str(host_token)[:8] eliminates the TypeError for integer tokens, and host_token is None correctly admits 0 as a valid token without affecting any other code path. The three new regression tests confirm all the described edge cases.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/server.py Two-line fix: converts token to string before slicing in the debug log, and replaces a truthiness guard with an explicit is None check. Both changes are correct and minimal.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_tool_search.py Adds three targeted regression tests (integer token, zero token, value preservation) using mocks only — no real network calls.

Reviews (1): Last reviewed commit: "fix(mcp): accept integer progressToken i..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will improve performance by 13.27%

⚡ 2 improved benchmarks
❌ 1 regressed benchmark
✅ 27 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_completion_multi_turn 3.1 ms 4.2 ms -25.23%
test_completion_simple_message 4.8 ms 3.2 ms +47.77%
test_completion_with_tools 4.2 ms 3.2 ms +31.54%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing litellm_mcp_int_progress_token (75755de) with litellm_internal_staging (db24027)

Open in CodSpeed

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@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 75755de. Configure here.

@mateo-berri mateo-berri 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.

LGTM; thanks!

@tin-berri
tin-berri merged commit f922be3 into litellm_internal_staging Jul 8, 2026
134 of 136 checks passed
@tin-berri
tin-berri deleted the litellm_mcp_int_progress_token branch July 8, 2026 04:46
mircea-pavel-anton added a commit to mirceanton/home-ops that referenced this pull request Jul 15, 2026
v1.92.0's MCP gateway crashes on every tool call with TypeError:
'int' object is not subscriptable, because _capture_host_progress_callback
slices progressToken assuming it's always a string. Claude Code sends
integer progress tokens (spec-valid per MCP: progressToken: str | int),
so every mcp_server_tool_call fails before reaching the backend server.

Upstream fix (BerriAI/litellm#32402) landed 2026-07-08 but hasn't been
cut into a stable release yet - only available in the v1.93.0-rc.1
prerelease image.
mircea-pavel-anton added a commit to mirceanton/home-ops that referenced this pull request Jul 16, 2026
v1.92.0's MCP gateway crashes on every tool call with TypeError:
'int' object is not subscriptable, because _capture_host_progress_callback
slices progressToken assuming it's always a string. Claude Code sends
integer progress tokens (spec-valid per MCP: progressToken: str | int),
so every mcp_server_tool_call fails before reaching the backend server.

Upstream fix (BerriAI/litellm#32402) landed 2026-07-08 but hasn't been
cut into a stable release yet - only available in the v1.93.0-rc.1
prerelease image.
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.

MCP tool calls fail with 'int' object is not subscriptable when client sends an integer progressToken

2 participants