Spaces:
Configuration error
Configuration error
""" | |
1. Allow proxy admin to perform create, update, and delete operations on MCP servers in the db. | |
2. Allows users to view the mcp servers they have access to. | |
Endpoints here: | |
- GET `/v1/mcp/server` - Returns all of the configured mcp servers in the db filtered by requestor's access | |
- GET `/v1/mcp/server/{server_id}` - Returns the the specific mcp server in the db given `server_id` filtered by requestor's access | |
- GET `/v1/mcp/server/{server_id}/tools` - Get all the tools from the mcp server specified by the `server_id` | |
- POST `/v1/mcp/server` - Add a new external mcp server. | |
- PUT `/v1/mcp/server` - Edits an existing mcp server. | |
- DELETE `/v1/mcp/server/{server_id}` - Deletes the mcp server given `server_id`. | |
""" | |
import importlib | |
from typing import Iterable, List, Optional | |
from fastapi import APIRouter, Depends, Header, HTTPException, Response, status | |
from fastapi.responses import JSONResponse | |
import litellm | |
from litellm._logging import verbose_logger, verbose_proxy_logger | |
from litellm.constants import LITELLM_PROXY_ADMIN_NAME | |
router = APIRouter(prefix="/v1/mcp", tags=["mcp"]) | |
MCP_AVAILABLE: bool = True | |
try: | |
importlib.import_module("mcp") | |
except ImportError as e: | |
verbose_logger.debug(f"MCP module not found: {e}") | |
MCP_AVAILABLE = False | |
if MCP_AVAILABLE: | |
from litellm.proxy._experimental.mcp_server.db import ( | |
create_mcp_server, | |
delete_mcp_server, | |
get_all_mcp_servers, | |
get_all_mcp_servers_for_user, | |
get_mcp_server, | |
update_mcp_server, | |
) | |
from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( | |
global_mcp_server_manager, | |
) | |
from litellm.proxy._types import ( | |
LiteLLM_MCPServerTable, | |
LitellmUserRoles, | |
NewMCPServerRequest, | |
SpecialMCPServerName, | |
UpdateMCPServerRequest, | |
UserAPIKeyAuth, | |
) | |
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth | |
from litellm.proxy.management_endpoints.common_utils import _user_has_admin_view | |
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper | |
def get_prisma_client_or_throw(message: str): | |
from litellm.proxy.proxy_server import prisma_client | |
if prisma_client is None: | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail={"error": message}, | |
) | |
return prisma_client | |
def does_mcp_server_exist( | |
mcp_server_records: Iterable[LiteLLM_MCPServerTable], mcp_server_id: str | |
) -> bool: | |
""" | |
Check if the mcp server with the given id exists in the iterable of mcp servers | |
""" | |
for mcp_server_record in mcp_server_records: | |
if mcp_server_record.server_id == mcp_server_id: | |
return True | |
return False | |
## FastAPI Routes | |
async def fetch_all_mcp_servers( | |
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), | |
): | |
""" | |
Get all of the configured mcp servers for the user in the db | |
``` | |
curl --location 'http://localhost:4000/v1/mcp/server' \ | |
--header 'Authorization: Bearer your_api_key_here' | |
``` | |
""" | |
prisma_client = get_prisma_client_or_throw( | |
"Database not connected. Connect a database to your proxy" | |
) | |
# perform authz check to filter the mcp servers user has access to | |
if _user_has_admin_view(user_api_key_dict): | |
return await get_all_mcp_servers(prisma_client) | |
# Find all mcp servers the user has access to | |
return await get_all_mcp_servers_for_user(prisma_client, user_api_key_dict) | |
async def fetch_mcp_server( | |
server_id: str, | |
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), | |
): | |
""" | |
Get the info on the mcp server specified by the `server_id` | |
Parameters: | |
- server_id: str - Required. The unique identifier of the mcp server to get info on. | |
``` | |
curl --location 'http://localhost:4000/v1/mcp/server/server_id' \ | |
--header 'Authorization: Bearer your_api_key_here' | |
``` | |
""" | |
prisma_client = get_prisma_client_or_throw( | |
"Database not connected. Connect a database to your proxy" | |
) | |
# check to see if server exists for all users | |
mcp_server = await get_mcp_server(prisma_client, server_id) | |
if mcp_server is None: | |
raise HTTPException( | |
status_code=status.HTTP_404_NOT_FOUND, | |
detail={"error": f"MCP Server with id {server_id} not found"}, | |
) | |
# Implement authz restriction from requested user | |
if _user_has_admin_view(user_api_key_dict): | |
return mcp_server | |
# Perform authz check to filter the mcp servers user has access to | |
mcp_server_records = await get_all_mcp_servers_for_user( | |
prisma_client, user_api_key_dict | |
) | |
exists = does_mcp_server_exist(mcp_server_records, server_id) | |
if exists: | |
global_mcp_server_manager.add_update_server(mcp_server) | |
return mcp_server | |
else: | |
raise HTTPException( | |
status_code=status.HTTP_403_FORBIDDEN, | |
detail={ | |
"error": f"User does not have permission to view mcp server with id {server_id}. You can only view mcp servers that you have access to." | |
}, | |
) | |
async def add_mcp_server( | |
payload: NewMCPServerRequest, | |
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), | |
litellm_changed_by: Optional[str] = Header( | |
None, | |
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", | |
), | |
): | |
""" | |
Allow users to add a new external mcp server. | |
""" | |
prisma_client = get_prisma_client_or_throw( | |
"Database not connected. Connect a database to your proxy" | |
) | |
# AuthZ - restrict only proxy admins to create mcp servers | |
if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role: | |
raise HTTPException( | |
status_code=status.HTTP_403_FORBIDDEN, | |
detail={ | |
"error": "User does not have permission to create mcp servers. You can only create mcp servers if you are a PROXY_ADMIN." | |
}, | |
) | |
elif payload.server_id is not None: | |
# fail if the mcp server with id already exists | |
mcp_server = await get_mcp_server(prisma_client, payload.server_id) | |
if mcp_server is not None: | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail={ | |
"error": f"MCP Server with id {payload.server_id} already exists. Cannot create another." | |
}, | |
) | |
elif ( | |
SpecialMCPServerName.all_team_servers == payload.server_id | |
or SpecialMCPServerName.all_proxy_servers == payload.server_id | |
): | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail={ | |
"error": f"MCP Server with id {payload.server_id} is special and cannot be used." | |
}, | |
) | |
# TODO: audit log for create | |
# Attempt to create the mcp server | |
try: | |
new_mcp_server = await create_mcp_server( | |
prisma_client, | |
payload, | |
touched_by=user_api_key_dict.user_id or LITELLM_PROXY_ADMIN_NAME, | |
) | |
global_mcp_server_manager.add_update_server(new_mcp_server) | |
except Exception as e: | |
verbose_proxy_logger.exception(f"Error creating mcp server: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail={"error": f"Error creating mcp server: {str(e)}"}, | |
) | |
return new_mcp_server | |
async def remove_mcp_server( | |
server_id: str, | |
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), | |
litellm_changed_by: Optional[str] = Header( | |
None, | |
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", | |
), | |
): | |
""" | |
Delete MCP Server from db and associated MCP related server entities. | |
Parameters: | |
- server_id: str - Required. The unique identifier of the mcp server to delete. | |
``` | |
curl -X "DELETE" --location 'http://localhost:4000/v1/mcp/server/server_id' \ | |
--header 'Authorization: Bearer your_api_key_here' | |
``` | |
""" | |
prisma_client = get_prisma_client_or_throw( | |
"Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys" | |
) | |
# Authz - restrict only admins to delete mcp servers | |
if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role: | |
raise HTTPException( | |
status_code=status.HTTP_403_FORBIDDEN, | |
detail={ | |
"error": "Call not allowed to delete MCP server. User is not a proxy admin. route={}".format( | |
"DELETE /v1/mcp/server" | |
) | |
}, | |
) | |
# try to delete the mcp server | |
mcp_server_record_deleted = await delete_mcp_server(prisma_client, server_id) | |
if mcp_server_record_deleted is None: | |
raise HTTPException( | |
status_code=status.HTTP_404_NOT_FOUND, | |
detail={"error": f"MCP Server not found, passed server_id={server_id}"}, | |
) | |
global_mcp_server_manager.remove_server(mcp_server_record_deleted) | |
# TODO: Enterprise: Finish audit log trail | |
if litellm.store_audit_logs: | |
pass | |
# TODO: Delete from virtual keys | |
# TODO: Delete from teams | |
# Update from global mcp store | |
return Response(status_code=status.HTTP_202_ACCEPTED) | |
async def edit_mcp_server( | |
payload: UpdateMCPServerRequest, | |
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), | |
litellm_changed_by: Optional[str] = Header( | |
None, | |
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", | |
), | |
): | |
""" | |
Updates the MCP Server in the db. | |
Parameters: | |
- payload: UpdateMCPServerRequest - Required. The updated mcp server data. | |
``` | |
curl -X "PUT" --location 'http://localhost:4000/v1/mcp/server' \ | |
--header 'Authorization: Bearer your_api_key_here' | |
``` | |
""" | |
prisma_client = get_prisma_client_or_throw( | |
"Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys" | |
) | |
# Authz - restrict only admins to delete mcp servers | |
if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role: | |
raise HTTPException( | |
status_code=status.HTTP_403_FORBIDDEN, | |
detail={ | |
"error": "Call not allowed to update MCP server. User is not a proxy admin. route={}".format( | |
"PUT /v1/mcp/server" | |
) | |
}, | |
) | |
# try to update the mcp server | |
mcp_server_record_updated = await update_mcp_server( | |
prisma_client, | |
payload, | |
touched_by=user_api_key_dict.user_id or LITELLM_PROXY_ADMIN_NAME, | |
) | |
if mcp_server_record_updated is None: | |
raise HTTPException( | |
status_code=status.HTTP_404_NOT_FOUND, | |
detail={ | |
"error": f"MCP Server not found, passed server_id={payload.server_id}" | |
}, | |
) | |
global_mcp_server_manager.add_update_server(mcp_server_record_updated) | |
# TODO: Enterprise: Finish audit log trail | |
if litellm.store_audit_logs: | |
pass | |
return mcp_server_record_updated | |