From e3e037c927414921824dcc9f48d52a1cfcc2cde9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 30 Jan 2026 20:17:33 +0000 Subject: [PATCH] fix(jwt-mcp): Add mcp_routes to default team_allowed_routes This fixes the issue where JWT auth was not selecting teams for MCP routes because MCP routes were not in the default team_allowed_routes. The flow was: 1. JWT auth calls find_team_with_model_access() 2. find_team_with_model_access checks allowed_routes_check() for the route 3. For MCP routes like /mcp/tools/list, this check FAILED because team_allowed_routes only included 'openai_routes' and 'info_routes' 4. Team was NOT selected, team_id was None 5. MCP permission lookup returned empty because team_id was None The fix adds 'mcp_routes' to the default team_allowed_routes so that: - Teams can be selected when accessing MCP endpoints - Team MCP permissions (from object_permission.mcp_servers) are enforced This is the second part of the fix for JWT + MCP permission enforcement. The first part (allowing teams with models=None) was in the previous commit. Changes: - LiteLLM_JWTAuth.team_allowed_routes: Added 'mcp_routes' to default list - allowed_routes_check: Updated fallback to also include 'mcp_routes' - Added test_jwt_auth_allows_mcp_routes_for_teams to verify the fix Co-authored-by: ishaan --- litellm/proxy/_types.py | 2 +- litellm/proxy/auth/auth_checks.py | 5 +- .../proxy/auth/test_handle_jwt.py | 57 +++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index bf99347ef6e..8ebf940b9ca 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3672,7 +3672,7 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): team_id_upsert: bool = False team_ids_jwt_field: Optional[str] = None upsert_sso_user_to_team: bool = False - team_allowed_routes: List[str] = ["openai_routes", "info_routes"] + team_allowed_routes: List[str] = ["openai_routes", "info_routes", "mcp_routes"] team_id_default: Optional[str] = Field( default=None, description="If no team_id given, default permissions/spend-tracking to this team.s", diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index e0b056d450f..67bebe0b86e 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -459,10 +459,11 @@ def allowed_routes_check( elif user_role == LitellmUserRoles.TEAM: if litellm_proxy_roles.team_allowed_routes is None: """ - By default allow a team to call openai + info routes + By default allow a team to call openai + info + mcp routes """ is_allowed = _allowed_routes_check( - user_route=user_route, allowed_routes=["openai_routes", "info_routes"] + user_route=user_route, + allowed_routes=["openai_routes", "info_routes", "mcp_routes"], ) return is_allowed elif litellm_proxy_roles.team_allowed_routes is not None: diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index e05792851a1..78978e6cd90 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -962,6 +962,63 @@ async def test_jwt_auth_sets_team_id_for_mcp_permission_lookup(monkeypatch): "Team should have object_permission_id for MCP permissions" +@pytest.mark.asyncio +async def test_jwt_auth_allows_mcp_routes_for_teams(monkeypatch): + """ + Test that JWT auth properly selects a team when accessing MCP routes. + + This tests the fix for the issue where MCP routes were not in the default + team_allowed_routes, causing teams to not be selected for MCP requests, + which meant team MCP permissions were not enforced. + """ + from litellm.caching import DualCache + from litellm.proxy.utils import ProxyLogging + import sys + import types + + proxy_server_module = types.ModuleType("proxy_server") + proxy_server_module.llm_router = None + monkeypatch.setitem(sys.modules, "litellm.proxy.proxy_server", proxy_server_module) + + # Team with models (standard team with model access) + team = LiteLLM_TeamTable( + team_id="team-with-models", + models=["gpt-4", "claude-sonnet"], + object_permission_id="obj-perm-456", + ) + + async def mock_get_team_object(*args, **kwargs): + return team + + monkeypatch.setattr( + "litellm.proxy.auth.handle_jwt.get_team_object", mock_get_team_object + ) + + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth() + + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + + # Test with MCP route - this should now work with the fix + team_id, team_obj = await JWTAuthManager.find_team_with_model_access( + team_ids={"team-with-models"}, + requested_model=None, # MCP requests don't specify a model + route="/mcp/tools/list", # MCP route - now allowed for teams + jwt_handler=jwt_handler, + prisma_client=None, + user_api_key_cache=user_api_key_cache, + parent_otel_span=None, + proxy_logging_obj=proxy_logging_obj, + ) + + # Team should be selected for MCP routes + assert team_id == "team-with-models", \ + "Team should be selected for MCP routes (mcp_routes now in team_allowed_routes)" + assert team_obj.team_id == "team-with-models" + assert team_obj.object_permission_id == "obj-perm-456" + + @pytest.mark.asyncio async def test_auth_builder_returns_team_membership_object(): """