-
-
Notifications
You must be signed in to change notification settings - Fork 6k
Description
Check for existing issues
- I have searched the existing issues and checked that my issue is not a duplicate.
What happened?
Summary
When a key has mcp_tool_permissions defined for a server but that server is NOT in mcp_servers, the server is excluded from the allowed list, making the tool permissions useless.
Current Behavior
If you create a key with:
object_permission = {
"mcp_servers": [], # Empty
"mcp_tool_permissions": {
"server_id_123": ["tool_a", "tool_b"]
}
}The server server_id_123 is NOT accessible because _get_allowed_mcp_servers_for_key() only checks mcp_servers and mcp_access_groups, ignoring mcp_tool_permissions.keys().
Expected Behavior
Servers referenced in mcp_tool_permissions should be implicitly included in the allowed servers list. If a user defines tool permissions for a server, they clearly intend to access that server.
Root Cause
Location: litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py lines 602-614
# Current code - only checks mcp_servers and mcp_access_groups
direct_mcp_servers = key_object_permission.mcp_servers or []
access_group_servers = await MCPRequestHandler._get_mcp_servers_from_access_groups(...)
# mcp_tool_permissions.keys() is NEVER considered!
all_servers = direct_mcp_servers + access_group_serversSame fix needed in:
_get_allowed_mcp_servers_for_team()_get_allowed_mcp_servers_for_end_user()
Steps to Reproduce
1. Setup MCP servers
MASTER_KEY="your-master-key"
# Create public server
curl -X POST "http://localhost:4000/v1/mcp/server" \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"server_name": "public_mcp_server",
"transport": "sse",
"url": "http://localhost:9999/sse",
"allow_all_keys": true
}'
# Create private server
curl -X POST "http://localhost:4000/v1/mcp/server" \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"server_name": "private_mcp_server",
"transport": "sse",
"url": "http://localhost:9998/sse",
"allow_all_keys": false
}'
# Get the server_id of private_mcp_server
curl "http://localhost:4000/v1/mcp/server" -H "Authorization: Bearer $MASTER_KEY"
# Note: server_id is a UUID like "aa1d8e71-318a-43cb-83ad-588d56470b1b"2. Create key with mcp_tool_permissions but empty mcp_servers
SERVER_ID="aa1d8e71-318a-43cb-83ad-588d56470b1b" # Use actual server_id from step 1
curl -X POST "http://localhost:4000/key/generate" \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d "{
\"key_alias\": \"bug_test\",
\"object_permission\": {
\"mcp_servers\": [],
\"mcp_tool_permissions\": {
\"$SERVER_ID\": [\"tool_a\", \"tool_b\"]
}
}
}"3. Verify permission was saved
SELECT mcp_servers, mcp_tool_permissions
FROM "LiteLLM_ObjectPermissionTable"
WHERE mcp_servers = '{}' AND mcp_tool_permissions IS NOT NULL;
-- Result:
-- mcp_servers | mcp_tool_permissions
-- {} | {"aa1d8e71-318a-43cb-83ad-588d56470b1b": ["tool_a", "tool_b"]}4. List MCP servers with the new key
curl "http://localhost:4000/v1/mcp/server" -H "Authorization: Bearer $NEW_KEY"5. Observe the bug
Expected: Both public_mcp_server and private_mcp_server visible
Actual: Only public_mcp_server visible
Test Scenario Executed (Confirmation)
Environment
- LiteLLM Version: v1.81.6
- Database: PostgreSQL 16
- Docker: Yes
Step 1: Create MCP Servers
Request - Create public server:
curl -X POST "http://localhost:4000/v1/mcp/server" \
-H "Authorization: Bearer 1234567890" \
-H "Content-Type: application/json" \
-d '{
"server_name": "public_mcp_server",
"transport": "sse",
"url": "http://localhost:9999/sse",
"allow_all_keys": true
}'Response:
{
"server_id": "e9ffaf61-16f8-4b33-8a01-e914f64d5343",
"server_name": "public_mcp_server",
"alias": "public_mcp_server",
"url": "http://localhost:9999/sse",
"transport": "sse",
"allow_all_keys": true
}Request - Create private server:
curl -X POST "http://localhost:4000/v1/mcp/server" \
-H "Authorization: Bearer 1234567890" \
-H "Content-Type: application/json" \
-d '{
"server_name": "private_mcp_server",
"transport": "sse",
"url": "http://localhost:9998/sse",
"allow_all_keys": false
}'Response:
{
"server_id": "aa1d8e71-318a-43cb-83ad-588d56470b1b",
"server_name": "private_mcp_server",
"alias": "private_mcp_server",
"url": "http://localhost:9998/sse",
"transport": "sse",
"allow_all_keys": false
}Step 2: Create Key with mcp_tool_permissions but empty mcp_servers
Request:
curl -X POST "http://localhost:4000/key/generate" \
-H "Authorization: Bearer 1234567890" \
-H "Content-Type: application/json" \
-d '{
"key_alias": "bug2_reproduction",
"object_permission": {
"mcp_servers": [],
"mcp_tool_permissions": {
"aa1d8e71-318a-43cb-83ad-588d56470b1b": ["tool_a", "tool_b"]
}
}
}'Response:
{
"key": "sk-eEnFa8wtbl2Ok4nbfDZ7Yw",
"key_alias": "bug2_reproduction",
"token": "...",
"object_permission": null
}Step 3: Verify in Database
Query:
SELECT mcp_servers, mcp_tool_permissions
FROM "LiteLLM_ObjectPermissionTable"
WHERE mcp_servers = '{}' AND mcp_tool_permissions IS NOT NULL;Result:
mcp_servers | mcp_tool_permissions
-------------+----------------------------------------------------------------
{} | {"aa1d8e71-318a-43cb-83ad-588d56470b1b": ["tool_a", "tool_b"]}
✅ Permission was saved correctly in database - mcp_tool_permissions contains the server_id with tool restrictions.
Step 4: List MCP Servers with Bug Key
Request:
curl "http://localhost:4000/v1/mcp/server" \
-H "Authorization: Bearer sk-eEnFa8wtbl2Ok4nbfDZ7Yw"Response:
[
{
"server_id": "e9ffaf61-16f8-4b33-8a01-e914f64d5343",
"server_name": "public_mcp_server",
"alias": "public_mcp_server",
"url": "http://localhost:9999/sse",
"transport": "sse",
"allow_all_keys": true
}
]Step 5: Bug Confirmation
| Expected | Actual |
|---|---|
2 servers: public_mcp_server + private_mcp_server |
1 server: only public_mcp_server |
❌ BUG CONFIRMED: The key has mcp_tool_permissions defined for private_mcp_server (server_id: aa1d8e71-318a-43cb-83ad-588d56470b1b), but the server is NOT visible in the list!
Comparison: Key WITH mcp_servers populated
For comparison, when creating a key with BOTH mcp_servers and mcp_tool_permissions:
Request:
curl -X POST "http://localhost:4000/key/generate" \
-H "Authorization: Bearer 1234567890" \
-H "Content-Type: application/json" \
-d '{
"key_alias": "working_key",
"object_permission": {
"mcp_servers": ["aa1d8e71-318a-43cb-83ad-588d56470b1b"],
"mcp_tool_permissions": {
"aa1d8e71-318a-43cb-83ad-588d56470b1b": ["tool_a"]
}
}
}'List servers with this key:
[
{
"server_id": "aa1d8e71-318a-43cb-83ad-588d56470b1b",
"server_name": "private_mcp_server",
"allow_all_keys": false
},
{
"server_id": "e9ffaf61-16f8-4b33-8a01-e914f64d5343",
"server_name": "public_mcp_server",
"allow_all_keys": true
}
]✅ Works correctly when mcp_servers is populated.
Also Affects UI Dashboard
This bug also manifests in the LiteLLM UI Dashboard when configuring MCP permissions for keys/teams.
When using the UI to set mcp_tool_permissions for a server without explicitly adding that server to mcp_servers, the configuration appears to save correctly but the server remains inaccessible.
User expectation: Selecting specific tools for a server in the UI should automatically grant access to that server.
Actual behavior: The server is not accessible unless manually added to mcp_servers list as well, which is not intuitive from the UI.
Relevant log output
Tested on LiteLLM v1.81.6
Database shows permission saved correctly:
mcp_servers: {}
mcp_tool_permissions: {"aa1d8e71-318a-43cb-83ad-588d56470b1b": ["tool_a", "tool_b"]}
But server is not accessible via API.What part of LiteLLM is this about?
Proxy
What LiteLLM version are you on ?
v1.81.6
Twitter / LinkedIn details
No response