fix(mcp): handle integer progressToken in host progress capture

Per the MCP spec, ProgressToken is `str | int`. The host progress
capture path logged the token with `f"...{host_token[:8]}..."`, which
raises `TypeError: 'int' object is not subscriptable` when a client
sends an integer progress token. The exception was swallowed by the
surrounding try/except and surfaced as a misleading "Could not capture
host progress context: 'int' object is not subscriptable" warning on
every such tool call.

The f-string is built eagerly regardless of log level, so the error
fired even with debug logging off. The progress callback itself is
assigned before the failing log line, so progress forwarding still
worked; the only effect was log noise.

Wrap the token in str() before slicing so both str and int tokens
format safely.
This commit is contained in:
Patrick Decat 2026-06-22 16:09:03 +02:00
parent 69b0dd2da0
commit cb7309c891
No known key found for this signature in database
GPG key ID: FD55B9BD5687D8FF
2 changed files with 55 additions and 1 deletions

View file

@ -714,7 +714,7 @@ if MCP_AVAILABLE:
host_progress_callback = forward_progress
verbose_logger.debug(
f"Host progressToken captured: {host_token[:8]}..."
f"Host progressToken captured: {str(host_token)[:8]}..."
)
except Exception as e:
verbose_logger.warning(f"Could not capture host progress context: {e}")

View file

@ -1,6 +1,7 @@
import asyncio
import contextvars
from datetime import datetime, timedelta
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@ -110,6 +111,59 @@ async def test_mcp_server_tool_call_body_contains_request_data():
assert body["arguments"] == tool_arguments
@pytest.mark.asyncio
async def test_mcp_server_tool_call_handles_integer_progress_token():
"""An integer progressToken must not break host progress capture.
Per the MCP spec ``ProgressToken = str | int``. A client sending an integer
token previously tripped ``f"...{host_token[:8]}..."`` with
``TypeError: 'int' object is not subscriptable``, which got swallowed into a
misleading "Could not capture host progress context" warning on every such
tool call. Regression test: that warning must not be emitted.
"""
try:
from mcp.server.lowlevel.server import request_ctx
from litellm.proxy._experimental.mcp_server.server import (
mcp_server_tool_call,
set_auth_context,
)
except ImportError:
pytest.skip("MCP server not available")
set_auth_context(UserAPIKeyAuth(api_key="test_key", user_id="test_user"))
# Drive the real request context the proxy reads, with an int progressToken
# (e.g. a client SDK that uses an incrementing counter).
host_ctx = SimpleNamespace(
meta=SimpleNamespace(progressToken=12345),
session=MagicMock(send_progress_notification=AsyncMock()),
)
ctx_token = request_ctx.set(host_ctx)
try:
with patch(
"litellm.proxy._experimental.mcp_server.server.verbose_logger"
) as mock_logger:
try:
await mcp_server_tool_call("test_tool", {"param": "value"})
except Exception:
# Downstream tool dispatch is out of scope; we only assert that
# capturing the host progress context did not warn.
pass
finally:
request_ctx.reset(ctx_token)
progress_warnings = [
call
for call in mock_logger.warning.call_args_list
if "Could not capture host progress context" in str(call)
]
assert not progress_warnings, (
"integer progressToken should be handled gracefully, "
f"got warning(s): {progress_warnings}"
)
def test_prepare_mcp_server_headers_case_insensitive_extra_headers():
try:
from litellm.proxy._experimental.mcp_server.server import (