From 1c92361284b6dfa05fa9f30872854c5ff3ca0186 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 06:59:21 +0000 Subject: [PATCH] test(mcp): align JWT registry ids with the MCP grammar and add a cold inactive-user case Co-Authored-By: bot_apk --- tests/e2e/AGENTS.md | 2 + tests/e2e/coverage_registry/mcp.yaml | 60 +++++++++++++------------- tests/e2e/mcp/test_mcp_jwt_auth_e2e.py | 35 ++++++++++++--- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/tests/e2e/AGENTS.md b/tests/e2e/AGENTS.md index 94035cfe849..d93a493397f 100644 --- a/tests/e2e/AGENTS.md +++ b/tests/e2e/AGENTS.md @@ -182,6 +182,8 @@ mcp... operation : list_tools | call_tool | list_resources | read_resource | list_prompts | get_prompt auth_family : none | api_key | bearer | oauth assertion : succeeds | denied_without_permission | persists_across_processes + | access_group_scoped | toolset_scoped | scoped | denied_invalid_signature + | denied_expired | denied_inactive_user | explicit_header_precedence e.g. mcp.call_tool.oauth.succeeds ``` diff --git a/tests/e2e/coverage_registry/mcp.yaml b/tests/e2e/coverage_registry/mcp.yaml index 6148182c90f..937b9b4da64 100644 --- a/tests/e2e/coverage_registry/mcp.yaml +++ b/tests/e2e/coverage_registry/mcp.yaml @@ -136,92 +136,92 @@ source: "user_api_key_auth_mcp.py:2137" rationale: "A key granted a toolset lists exactly the toolset's tools: the rest of the server's catalog stays hidden and every stored name resolves" -- id: mcp.list_tools.bearer.jwt_valid_scoped +- id: mcp.list_tools.bearer.scoped module: mcp tier: P0 operation: list_tools auth_family: bearer - assertions: [valid_scoped] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [scoped] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.call_tool.bearer.jwt_valid_scoped +- id: mcp.call_tool.bearer.scoped module: mcp tier: P0 operation: call_tool auth_family: bearer - assertions: [valid_scoped] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [scoped] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.list_tools.bearer.jwt_invalid_signature_denied +- id: mcp.list_tools.bearer.denied_invalid_signature module: mcp tier: P0 operation: list_tools auth_family: bearer - assertions: [invalid_signature_denied] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [denied_invalid_signature] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.call_tool.bearer.jwt_invalid_signature_denied +- id: mcp.call_tool.bearer.denied_invalid_signature module: mcp tier: P0 operation: call_tool auth_family: bearer - assertions: [invalid_signature_denied] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [denied_invalid_signature] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.list_tools.bearer.jwt_expired_denied +- id: mcp.list_tools.bearer.denied_expired module: mcp tier: P0 operation: list_tools auth_family: bearer - assertions: [expired_denied] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [denied_expired] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.call_tool.bearer.jwt_expired_denied +- id: mcp.call_tool.bearer.denied_expired module: mcp tier: P0 operation: call_tool auth_family: bearer - assertions: [expired_denied] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [denied_expired] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.list_tools.bearer.jwt_inactive_user_denied +- id: mcp.list_tools.bearer.denied_inactive_user module: mcp tier: P0 operation: list_tools auth_family: bearer - assertions: [inactive_user_denied] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [denied_inactive_user] + source: "user_api_key_auth.py:1722" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.call_tool.bearer.jwt_inactive_user_denied +- id: mcp.call_tool.bearer.denied_inactive_user module: mcp tier: P0 operation: call_tool auth_family: bearer - assertions: [inactive_user_denied] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [denied_inactive_user] + source: "user_api_key_auth.py:1722" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.list_tools.bearer.jwt_header_precedence +- id: mcp.list_tools.bearer.explicit_header_precedence module: mcp tier: P0 operation: list_tools auth_family: bearer - assertions: [header_precedence] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [explicit_header_precedence] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" -- id: mcp.call_tool.bearer.jwt_header_precedence +- id: mcp.call_tool.bearer.explicit_header_precedence module: mcp tier: P0 operation: call_tool auth_family: bearer - assertions: [header_precedence] - source: "test_mcp_jwt_auth_e2e.py" + assertions: [explicit_header_precedence] + source: "user_api_key_auth.py" rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials" diff --git a/tests/e2e/mcp/test_mcp_jwt_auth_e2e.py b/tests/e2e/mcp/test_mcp_jwt_auth_e2e.py index c0c39fc1858..1894876acc8 100644 --- a/tests/e2e/mcp/test_mcp_jwt_auth_e2e.py +++ b/tests/e2e/mcp/test_mcp_jwt_auth_e2e.py @@ -86,7 +86,7 @@ def granted_mcp(client: McpClient, resources: ResourceManager, jwt_identity: Ide class TestMcpGatewayJwt: - @pytest.mark.covers("mcp.list_tools.bearer.jwt_valid_scoped", "mcp.call_tool.bearer.jwt_valid_scoped") + @pytest.mark.covers("mcp.list_tools.bearer.scoped", "mcp.call_tool.bearer.scoped") @pytest.mark.parametrize("header", ("authorization", "x_litellm_api_key")) def test_valid_scoped_jwt_lists_and_calls( self, client: McpClient, granted_mcp: GrantedMcp, header: Literal["authorization", "x_litellm_api_key"] @@ -99,7 +99,7 @@ class TestMcpGatewayJwt: _allowed(client, granted_mcp, headers) @pytest.mark.covers( - "mcp.list_tools.bearer.jwt_invalid_signature_denied", "mcp.call_tool.bearer.jwt_invalid_signature_denied" + "mcp.list_tools.bearer.denied_invalid_signature", "mcp.call_tool.bearer.denied_invalid_signature" ) def test_tampered_signature_denies_both_operations(self, client: McpClient, granted_mcp: GrantedMcp) -> None: valid: Final = AuthHeaders(authorization=f"Bearer {granted_mcp.token}") @@ -110,7 +110,7 @@ class TestMcpGatewayJwt: _denied(client, granted_mcp, AuthHeaders(authorization=f"Bearer {tampered}"), "signature") _allowed(client, granted_mcp, valid) - @pytest.mark.covers("mcp.list_tools.bearer.jwt_expired_denied", "mcp.call_tool.bearer.jwt_expired_denied") + @pytest.mark.covers("mcp.list_tools.bearer.denied_expired", "mcp.call_tool.bearer.denied_expired") def test_expired_signed_jwt_denies_both_operations( self, client: McpClient, granted_mcp: GrantedMcp, jwt_identity: Identity, idp: Keycloak ) -> None: @@ -125,9 +125,7 @@ class TestMcpGatewayJwt: _denied(client, granted_mcp, AuthHeaders(authorization=f"Bearer {expiring}"), "expired") _allowed(client, granted_mcp, valid) - @pytest.mark.covers( - "mcp.list_tools.bearer.jwt_inactive_user_denied", "mcp.call_tool.bearer.jwt_inactive_user_denied" - ) + @pytest.mark.covers("mcp.list_tools.bearer.denied_inactive_user", "mcp.call_tool.bearer.denied_inactive_user") def test_deactivated_user_cannot_reuse_warm_jwt( self, client: McpClient, granted_mcp: GrantedMcp, jwt_identity: Identity ) -> None: @@ -149,7 +147,30 @@ class TestMcpGatewayJwt: ) _allowed(client, granted_mcp, headers) - @pytest.mark.covers("mcp.list_tools.bearer.jwt_header_precedence", "mcp.call_tool.bearer.jwt_header_precedence") + @pytest.mark.covers("mcp.list_tools.bearer.denied_inactive_user", "mcp.call_tool.bearer.denied_inactive_user") + def test_deactivated_user_is_denied_on_a_cold_jwt( + self, client: McpClient, granted_mcp: GrantedMcp, jwt_identity: Identity, idp: Keycloak + ) -> None: + management: Final = build_management_client(client.proxy) + management.update_user( + UserUpdateBody( + user_id=jwt_identity.user_id, user_role="internal_user", metadata=UserScimMetadata(scim_active=False) + ) + ) + cold: Final = idp.access_token(jwt_identity) + assert cold != granted_mcp.token + access: Final = GrantedMcp(granted_mcp.server_id, granted_mcp.other_server_id, granted_mcp.tool, cold) + _denied(client, access, AuthHeaders(authorization=f"Bearer {cold}"), "deactivated") + management.update_user( + UserUpdateBody( + user_id=jwt_identity.user_id, user_role="internal_user", metadata=UserScimMetadata(scim_active=True) + ) + ) + _allowed(client, access, AuthHeaders(authorization=f"Bearer {cold}")) + + @pytest.mark.covers( + "mcp.list_tools.bearer.explicit_header_precedence", "mcp.call_tool.bearer.explicit_header_precedence" + ) def test_explicit_gateway_header_wins_without_fallback( self, client: McpClient, granted_mcp: GrantedMcp, resources: ResourceManager ) -> None: