mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
478 lines
16 KiB
YAML
478 lines
16 KiB
YAML
# MCP module. Grounded in litellm/proxy/_experimental/mcp_server/. See tests/e2e/CLAUDE.md for the grammar.
|
|
- id: mcp.list_tools.api_key.succeeds
|
|
module: mcp
|
|
tier: P0
|
|
operation: list_tools
|
|
auth_family: api_key
|
|
assertions: [succeeds]
|
|
source: "server.py:637"
|
|
rationale: Core operation; most common auth path; high usage
|
|
- id: mcp.list_tools.api_key.access_group_scoped
|
|
module: mcp
|
|
tier: P1
|
|
operation: list_tools
|
|
auth_family: api_key
|
|
assertions: [access_group_scoped]
|
|
source: "test_mcp_access_group_e2e.py"
|
|
rationale: "A key granted an MCP access group sees the tagged server's tools; a key with a different group does not. Access-group-scoped tool selection at key creation"
|
|
- id: mcp.list_tools.api_key.denied_without_permission
|
|
module: mcp
|
|
tier: P0
|
|
operation: list_tools
|
|
auth_family: api_key
|
|
assertions: [denied_without_permission]
|
|
source: "mcp_server_manager.py:1409"
|
|
rationale: Permission guard is high blast-radius; multi-tenant safety
|
|
- id: mcp.call_tool.api_key.succeeds
|
|
module: mcp
|
|
tier: P0
|
|
operation: call_tool
|
|
auth_family: api_key
|
|
assertions: [succeeds]
|
|
source: "server.py:849"
|
|
rationale: Primary operation; customer-critical; high usage
|
|
- id: mcp.call_tool.api_key.denied_without_permission
|
|
module: mcp
|
|
tier: P0
|
|
operation: call_tool
|
|
auth_family: api_key
|
|
assertions: [denied_without_permission]
|
|
source: "rest_endpoints.py:305-386"
|
|
rationale: Tool-level permission guard; multi-tenant safety
|
|
- id: mcp.list_tools.bearer.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: list_tools
|
|
auth_family: bearer
|
|
assertions: [succeeds]
|
|
source: "server.py:662"
|
|
rationale: OAuth/bearer token flow; upstream delegation
|
|
- id: mcp.call_tool.bearer.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: call_tool
|
|
auth_family: bearer
|
|
assertions: [succeeds]
|
|
source: "server.py:886"
|
|
rationale: Bearer token forwarding for tool invocation
|
|
- id: mcp.list_tools.oauth.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: list_tools
|
|
auth_family: oauth
|
|
assertions: [succeeds]
|
|
source: "rest_endpoints.py:138-188"
|
|
rationale: Interactive OAuth2 flow; live token management
|
|
- id: mcp.call_tool.oauth.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: call_tool
|
|
auth_family: oauth
|
|
assertions: [succeeds]
|
|
source: "db.py user_oauth_credential lookup"
|
|
rationale: OAuth2 token passthrough; per-user credential storage
|
|
- id: mcp.list_tools.none.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: list_tools
|
|
auth_family: none
|
|
assertions: [succeeds]
|
|
source: "mcp_server_manager.py:1485-1492"
|
|
rationale: Explicitly anonymous true_passthrough servers
|
|
- id: mcp.call_tool.none.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: call_tool
|
|
auth_family: none
|
|
assertions: [succeeds]
|
|
source: "rest_endpoints.py:305-334"
|
|
rationale: No upstream auth required; demo servers
|
|
- id: mcp.get_prompt.api_key.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: get_prompt
|
|
auth_family: api_key
|
|
assertions: [succeeds]
|
|
source: "server.py:1042"
|
|
rationale: Prompt op; same auth stack as tools
|
|
- id: mcp.read_resource.api_key.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: read_resource
|
|
auth_family: api_key
|
|
assertions: [succeeds]
|
|
source: "server.py:1177"
|
|
rationale: Resource op; same permission model as tools
|
|
- id: mcp.list_prompts.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: list_prompts
|
|
auth_family: api_key
|
|
assertions: [succeeds]
|
|
source: "server.py:993"
|
|
rationale: Smoke-level; same auth stack as list_tools
|
|
- id: mcp.list_resources.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: list_resources
|
|
auth_family: api_key
|
|
assertions: [succeeds]
|
|
source: "server.py:1089"
|
|
rationale: Smoke; rarely used; same auth model as tools
|
|
- id: mcp.list_tools.api_key.toolset_scoped
|
|
module: mcp
|
|
tier: P0
|
|
operation: list_tools
|
|
auth_family: api_key
|
|
assertions: [toolset_scoped]
|
|
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_resource_templates.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: list_resource_templates
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/_experimental/mcp_server/server.py:list_resource_templates
|
|
rationale: Resource-template listing; same auth stack as tools
|
|
- id: mcp.server_add.api_key.admin_only
|
|
module: mcp
|
|
tier: P0
|
|
operation: server_add
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
- persists
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:add_mcp_server
|
|
rationale: PROXY_ADMIN-gated server registration; persists to DB and is picked up without restart; onboarding path
|
|
- id: mcp.server_list.api_key.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: server_list
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:fetch_all_mcp_servers
|
|
rationale: List registered servers; admin required only to query another team
|
|
- id: mcp.server_get.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: server_get
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:fetch_mcp_server
|
|
rationale: Fetch one server; non-admins filtered to their allowed servers
|
|
- id: mcp.server_update.api_key.admin_only
|
|
module: mcp
|
|
tier: P1
|
|
operation: server_update
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
- persists
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:edit_mcp_server
|
|
rationale: PROXY_ADMIN-gated edit persists to DB
|
|
- id: mcp.server_delete.api_key.admin_only
|
|
module: mcp
|
|
tier: P1
|
|
operation: server_delete
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:remove_mcp_server
|
|
rationale: PROXY_ADMIN-gated delete removes the server row
|
|
- id: mcp.server_health.api_key.reports_status
|
|
module: mcp
|
|
tier: P1
|
|
operation: server_health
|
|
auth_family: api_key
|
|
assertions:
|
|
- reports_status
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:health_check_servers
|
|
rationale: Health check distinguishes a reachable upstream from a broken one
|
|
- id: mcp.server_submit.api_key.creates_pending
|
|
module: mcp
|
|
tier: P1
|
|
operation: server_submit
|
|
auth_family: api_key
|
|
assertions:
|
|
- creates_pending
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:register_mcp_server
|
|
rationale: Non-admin submission creates a pending row for review; admins auto-approve
|
|
- id: mcp.server_approve.api_key.admin_only
|
|
module: mcp
|
|
tier: P1
|
|
operation: server_approve
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:approve_mcp_server_submission
|
|
rationale: PROXY_ADMIN approves a pending submission into an active server
|
|
- id: mcp.server_reject.api_key.admin_only
|
|
module: mcp
|
|
tier: P2
|
|
operation: server_reject
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:reject_mcp_server_submission
|
|
rationale: PROXY_ADMIN rejects a pending submission
|
|
- id: mcp.make_public.api_key.admin_only
|
|
module: mcp
|
|
tier: P2
|
|
operation: make_public
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:make_mcp_servers_public
|
|
rationale: PROXY_ADMIN marks servers public for the AI Hub
|
|
- id: mcp.oauth_authorize.oauth.issues_code
|
|
module: mcp
|
|
tier: P1
|
|
operation: oauth_authorize
|
|
auth_family: oauth
|
|
assertions:
|
|
- issues_code
|
|
source: litellm/proxy/_experimental/mcp_server/byok_oauth_endpoints.py:byok_authorize_post
|
|
rationale: Authenticated authorize stores a one-time code bound to user_id + PKCE challenge and redirects with code+state
|
|
- id: mcp.oauth_token.oauth.exchanges_code
|
|
module: mcp
|
|
tier: P1
|
|
operation: oauth_token
|
|
auth_family: oauth
|
|
assertions:
|
|
- exchanges_code
|
|
source: litellm/proxy/_experimental/mcp_server/byok_oauth_endpoints.py:byok_token
|
|
rationale: Valid code + PKCE verifier mints a byok_session JWT and persists the per-user upstream credential
|
|
- id: mcp.oauth_token.oauth.rejects_bad_pkce
|
|
module: mcp
|
|
tier: P0
|
|
operation: oauth_token
|
|
auth_family: oauth
|
|
assertions:
|
|
- rejects_bad_pkce
|
|
source: litellm/proxy/_experimental/mcp_server/byok_oauth_endpoints.py:byok_token
|
|
rationale: S256 verifier mismatch returns invalid_grant; the PKCE trust boundary must not be bypassable
|
|
- id: mcp.oauth_token.oauth.rejects_reused_code
|
|
module: mcp
|
|
tier: P1
|
|
operation: oauth_token
|
|
auth_family: oauth
|
|
assertions:
|
|
- rejects_reused_code
|
|
source: litellm/proxy/_experimental/mcp_server/byok_oauth_endpoints.py:byok_token
|
|
rationale: Authorization code is one-time-use; a replay is refused
|
|
- id: mcp.oauth_metadata.none.discoverable
|
|
module: mcp
|
|
tier: P2
|
|
operation: oauth_metadata
|
|
auth_family: none
|
|
assertions:
|
|
- discoverable
|
|
source: litellm/proxy/_experimental/mcp_server/byok_oauth_endpoints.py:oauth_authorization_server_metadata
|
|
rationale: Public .well-known AS + protected-resource metadata advertise S256 + authorization_code so MCP clients discover
|
|
the flow
|
|
- id: mcp.user_credential_set.api_key.persists
|
|
module: mcp
|
|
tier: P1
|
|
operation: user_credential_set
|
|
auth_family: api_key
|
|
assertions:
|
|
- persists
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:store_mcp_user_credential
|
|
rationale: Caller stores its own BYOK key for a server; injected at egress on later tool calls
|
|
- id: mcp.user_credential_delete.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: user_credential_delete
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:delete_mcp_user_credential
|
|
rationale: Caller revokes its own stored BYOK key
|
|
- id: mcp.oauth_user_credential_status.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: oauth_user_credential_status
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:get_mcp_oauth_user_credential_status
|
|
rationale: Caller checks whether it has a stored upstream OAuth2 credential
|
|
- id: mcp.user_env_vars_set.api_key.rejects_undeclared
|
|
module: mcp
|
|
tier: P2
|
|
operation: user_env_vars_set
|
|
auth_family: api_key
|
|
assertions:
|
|
- persists
|
|
- rejects_undeclared
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:store_mcp_user_env_vars
|
|
rationale: Per-user env vars persist, but only admin-declared var names are accepted
|
|
- id: mcp.tools_rest.api_key.succeeds
|
|
module: mcp
|
|
tier: P1
|
|
operation: tools_rest
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:get_mcp_tools
|
|
rationale: Flat REST tool listing scoped to the calling key's allowed servers
|
|
- id: mcp.tool_search.api_key.denied_without_permission
|
|
module: mcp
|
|
tier: P1
|
|
operation: tool_search
|
|
auth_family: api_key
|
|
assertions:
|
|
- denied_without_permission
|
|
source: litellm/proxy/_experimental/mcp_server/tool_search.py:handle_mcp_tool_search
|
|
rationale: Semantic tool-search virtual tool is gated by object_permission.mcp_tool_search_enabled
|
|
- id: mcp.tool_search.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: tool_search
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/_experimental/mcp_server/tool_search.py:handle_mcp_tool_search
|
|
rationale: Semantic tool search returns ranked tools for a permitted key
|
|
- id: mcp.access_groups.api_key.succeeds
|
|
module: mcp
|
|
tier: P2
|
|
operation: access_groups
|
|
auth_family: api_key
|
|
assertions:
|
|
- succeeds
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:get_mcp_access_groups
|
|
rationale: List MCP access groups the key can see
|
|
- id: mcp.discover.api_key.admin_only
|
|
module: mcp
|
|
tier: P2
|
|
operation: discover
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:discover_mcp_servers
|
|
rationale: PROXY_ADMIN-only curated well-known server discovery for the UI
|
|
- id: mcp.registry_json.none.public_filtered
|
|
module: mcp
|
|
tier: P2
|
|
operation: registry_json
|
|
auth_family: none
|
|
assertions:
|
|
- public_filtered
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:get_mcp_registry
|
|
rationale: Public registry is gated by a feature flag and IP-filtered to public servers for external callers
|
|
- id: mcp.toolset_add.api_key.admin_only
|
|
module: mcp
|
|
tier: P2
|
|
operation: toolset_add
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:add_mcp_toolset
|
|
rationale: PROXY_ADMIN creates a toolset
|
|
- id: mcp.toolset_list.api_key.scoped
|
|
module: mcp
|
|
tier: P2
|
|
operation: toolset_list
|
|
auth_family: api_key
|
|
assertions:
|
|
- scoped
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:fetch_mcp_toolsets
|
|
rationale: Toolset listing is filtered to the key's object_permission.mcp_toolsets
|
|
- id: mcp.toolset_delete.api_key.admin_only
|
|
module: mcp
|
|
tier: P2
|
|
operation: toolset_delete
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:remove_mcp_toolset
|
|
rationale: PROXY_ADMIN deletes a toolset
|
|
- id: mcp.oauth_session.api_key.admin_only
|
|
module: mcp
|
|
tier: P2
|
|
operation: oauth_session
|
|
auth_family: api_key
|
|
assertions:
|
|
- admin_only
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:add_session_mcp_server
|
|
rationale: PROXY_ADMIN caches a temp in-memory server for the OAuth setup flow (no DB write)
|
|
- id: mcp.upstream_authorize.oauth.redirects_upstream
|
|
module: mcp
|
|
tier: P2
|
|
operation: upstream_authorize
|
|
auth_family: oauth
|
|
assertions:
|
|
- redirects_upstream
|
|
source: litellm/proxy/management_endpoints/mcp_management_endpoints.py:mcp_authorize
|
|
rationale: Gateway-managed authorization_code begins the upstream OAuth authorize for a server; the delegate-auth leg needs
|
|
a real OAuth upstream to prove end to end
|
|
- id: mcp.list_tools.api_key.team_toolset_scoped
|
|
module: mcp
|
|
tier: P0
|
|
operation: list_tools
|
|
auth_family: api_key
|
|
assertions:
|
|
- toolset_scoped
|
|
source: tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py:TestMcpToolsetEnforcementPerLevel
|
|
rationale: 'A toolset attached to a team''s object_permission narrows the team key''s tools/list to exactly the tools it
|
|
names (LIT-5749: stored but inert before the fix)'
|
|
fail_before_fix: proven
|
|
- id: mcp.call_tool.api_key.team_toolset_denied_outside
|
|
module: mcp
|
|
tier: P0
|
|
operation: call_tool
|
|
auth_family: api_key
|
|
assertions:
|
|
- denied_outside_toolset
|
|
source: tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py:TestMcpToolsetEnforcementPerLevel
|
|
rationale: A team key calling a tool on the granted server but outside the team's toolset is refused; the named tool stays
|
|
callable (LIT-5749)
|
|
fail_before_fix: proven
|
|
- id: mcp.list_tools.api_key.org_toolset_scoped
|
|
module: mcp
|
|
tier: P1
|
|
operation: list_tools
|
|
auth_family: api_key
|
|
assertions:
|
|
- toolset_scoped
|
|
source: tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py:TestMcpToolsetEnforcementPerLevel
|
|
rationale: 'A toolset on an org''s object_permission caps a member team''s key that declares no MCP grants of its own (LIT-5749:
|
|
the org substitution amplifier)'
|
|
fail_before_fix: proven
|
|
- id: mcp.call_tool.api_key.org_toolset_denied_outside
|
|
module: mcp
|
|
tier: P1
|
|
operation: call_tool
|
|
auth_family: api_key
|
|
assertions:
|
|
- denied_outside_toolset
|
|
source: tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py:TestMcpToolsetEnforcementPerLevel
|
|
rationale: An org-inherited key calling outside the org's toolset is refused while the named tool stays callable (LIT-5749)
|
|
fail_before_fix: proven
|
|
- id: mcp.list_tools.api_key.user_toolset_scoped
|
|
module: mcp
|
|
tier: P1
|
|
operation: list_tools
|
|
auth_family: api_key
|
|
assertions:
|
|
- toolset_scoped
|
|
source: tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py:TestMcpToolsetEnforcementPerLevel
|
|
rationale: A toolset on an internal user's row ceils the servers the user's own key grants; user-level toolsets narrow,
|
|
never grant (LIT-5749)
|
|
fail_before_fix: proven
|
|
- id: mcp.call_tool.api_key.user_toolset_denied_outside
|
|
module: mcp
|
|
tier: P1
|
|
operation: call_tool
|
|
auth_family: api_key
|
|
assertions:
|
|
- denied_outside_toolset
|
|
source: tests/e2e/mcp/test_mcp_toolset_enforcement_e2e.py:TestMcpToolsetEnforcementPerLevel
|
|
rationale: A key whose user row holds a toolset is refused calling outside it even though the key itself grants the whole
|
|
server (LIT-5749)
|
|
fail_before_fix: proven
|