From 9927d458bcff2a8109f8a8c0d01fc340024ffd81 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Thu, 25 Jun 2026 23:51:46 -0700 Subject: [PATCH] refactor(mcp): delete the unwired V1PerUserTokenStore adapter (step 1b piece 5) Piece 4 replaced V1PerUserTokenStore with the v2-native chain at the composition root, leaving the adapter with no callers, so remove it and its test. The shared v1 read/refresh core (resolve_user_oauth_access_token and friends) stays - delegate's egress in server.py still uses it - and comes out with the delegate migration. --- .../outbound_credentials/v1_token_store.py | 49 ------------------- .../test_v1_token_store.py | 47 ------------------ 2 files changed, 96 deletions(-) delete mode 100644 litellm/proxy/_experimental/mcp_server/outbound_credentials/v1_token_store.py delete mode 100644 tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_v1_token_store.py diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/v1_token_store.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/v1_token_store.py deleted file mode 100644 index c5e2f1f266f..00000000000 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/v1_token_store.py +++ /dev/null @@ -1,49 +0,0 @@ -"""v1-backed ``OAuthTokenStore`` source for the ``authorization_code`` mode. - -Resolves the user's access token through v1's egress core (``resolve_user_oauth_access_token``: -Redis cache, else DB read with refresh), so the v2 arm injects exactly the token v1 would, with the -same silent refresh. This is the strangler adapter; step 1b swaps the core for a v2-native store -behind the same ``OAuthTokenStore`` seam. It imports v1, so it is kept out of the package -``__init__`` like the rest of the adapter layer. -""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import TYPE_CHECKING - -from litellm.proxy._experimental.mcp_server.outbound_credentials.oauth_token_store import ( - OAuthToken, -) - -if TYPE_CHECKING: - from litellm.types.mcp_server.mcp_server_manager import MCPServer - - -class V1PerUserTokenStore: - """``OAuthTokenStore`` backed by v1's per-user OAuth egress. - - ``fetch`` looks the server up by id (injected ``server_lookup``) and resolves the token through - v1's ``resolve_user_oauth_access_token``, which refreshes an expired token when a refresh_token - is stored and re-warms the Redis cache. Returns ``None`` when the user has no usable token (the - arm turns that into a challenge); v1's core swallows store errors as a miss, so this never - raises ``TokenStoreUnavailable``. The returned ``OAuthToken`` carries only the access token — - refresh happens inside the core, not via ``RefreshingTokenStore``. - """ - - def __init__(self, server_lookup: Callable[[str], MCPServer | None]) -> None: - self._server_lookup = server_lookup - - async def fetch(self, user_id: str, server_id: str) -> OAuthToken | None: - if not user_id: - return None - server = self._server_lookup(server_id) - if server is None: - return None - - from litellm.proxy._experimental.mcp_server.db import ( - resolve_user_oauth_access_token, - ) - - access_token = await resolve_user_oauth_access_token(user_id, server) - return OAuthToken(access_token=access_token) if access_token else None diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_v1_token_store.py b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_v1_token_store.py deleted file mode 100644 index 2595fceef09..00000000000 --- a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_v1_token_store.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Tests for the v1-backed per-user OAuth token source (V1PerUserTokenStore).""" - -from unittest.mock import AsyncMock, patch - -from litellm.proxy._experimental.mcp_server.outbound_credentials.v1_token_store import ( - V1PerUserTokenStore, -) - -_RESOLVE = "litellm.proxy._experimental.mcp_server.db.resolve_user_oauth_access_token" - - -def _store_for(server: object) -> V1PerUserTokenStore: - return V1PerUserTokenStore(server_lookup=lambda _server_id: server) - - -async def test_wraps_the_resolved_access_token(): - with patch(_RESOLVE, new=AsyncMock(return_value="at-123")): - token = await _store_for(object()).fetch("alice", "s") - assert token is not None and token.access_token == "at-123" - - -async def test_missing_token_is_none(): - with patch(_RESOLVE, new=AsyncMock(return_value=None)): - assert await _store_for(object()).fetch("alice", "s") is None - - -async def test_empty_user_short_circuits_without_resolving(): - resolve = AsyncMock(return_value="at") - with patch(_RESOLVE, new=resolve): - assert await _store_for(object()).fetch("", "s") is None - resolve.assert_not_called() - - -async def test_unknown_server_is_none_without_resolving(): - resolve = AsyncMock(return_value="at") - store = V1PerUserTokenStore(server_lookup=lambda _server_id: None) - with patch(_RESOLVE, new=resolve): - assert await store.fetch("alice", "missing") is None - resolve.assert_not_called() - - -async def test_passes_user_and_resolved_server_to_the_core(): - server = object() - resolve = AsyncMock(return_value="at") - with patch(_RESOLVE, new=resolve): - await _store_for(server).fetch("alice", "srv-1") - resolve.assert_awaited_once_with("alice", server)