fix(mcp): use WeakValueDictionary for token exchange locks to prevent memory leak

Per-user lock entries are now eligible for GC once no coroutine holds
a reference, so rotating JWTs across many users don't accumulate entries
indefinitely. Also moved IDP error body to debug log only to avoid
leaking partial token values or internal metadata into server error replies.
This commit is contained in:
Ishaan Jaffer 2026-05-01 11:24:42 -07:00
parent 2fe7110a8c
commit ba95b204c7
No known key found for this signature in database
2 changed files with 18 additions and 24 deletions

View file

@ -10,6 +10,7 @@ See: https://datatracker.ietf.org/doc/html/rfc8693
import asyncio
import hashlib
import weakref
from typing import TYPE_CHECKING, Dict, Optional, Tuple
import httpx
@ -46,10 +47,18 @@ class TokenExchangeHandler:
max_size_in_memory=MCP_TOKEN_EXCHANGE_CACHE_MAX_SIZE,
default_ttl=MCP_OAUTH2_TOKEN_CACHE_DEFAULT_TTL,
)
self._locks: Dict[str, asyncio.Lock] = {}
# WeakValueDictionary so locks are GC'd once no coroutine holds a reference,
# preventing unbounded growth with many rotating user tokens.
self._locks: weakref.WeakValueDictionary[str, asyncio.Lock] = (
weakref.WeakValueDictionary()
)
def _get_lock(self, cache_key: str) -> asyncio.Lock:
return self._locks.setdefault(cache_key, asyncio.Lock())
lock = self._locks.get(cache_key)
if lock is None:
lock = asyncio.Lock()
self._locks[cache_key] = lock
return lock
@staticmethod
def _cache_key(subject_token: str, server_id: str) -> str:
@ -131,10 +140,15 @@ class TokenExchangeHandler:
response = await client.post(endpoint, data=data)
response.raise_for_status()
except httpx.HTTPStatusError as exc:
verbose_logger.debug(
"Token exchange IDP error for MCP server %s (status %d): %s",
server.server_id,
exc.response.status_code,
exc.response.text,
)
raise ValueError(
f"Token exchange for MCP server '{server.server_id}' "
f"failed with status {exc.response.status_code}: "
f"{exc.response.text}"
f"failed with status {exc.response.status_code}"
) from exc
body = response.json()

View file

@ -1,20 +0,0 @@
model_list:
- model_name: gpt-4o
litellm_params:
model: gpt-4o
mcp_servers:
test_obo:
url: http://localhost:9998/mcp
transport: http
auth_type: oauth2_token_exchange
token_exchange_endpoint: http://localhost:9999/oauth2/token
audience: api://test-mcp-server
scopes:
- mcp.tools.read
- mcp.tools.execute
client_id: litellm-client
client_secret: litellm-secret
general_settings:
master_key: sk-1234