Spaces:
Configuration error
Configuration error
File size: 13,747 Bytes
447ebeb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
"""
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
@router.get(
"/server",
description="Returns the mcp server list",
dependencies=[Depends(user_api_key_auth)],
response_model=List[LiteLLM_MCPServerTable],
)
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)
@router.get(
"/server/{server_id}",
description="Returns the mcp server info",
dependencies=[Depends(user_api_key_auth)],
response_model=LiteLLM_MCPServerTable,
)
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."
},
)
@router.post(
"/server",
description="Allows creation of mcp servers",
dependencies=[Depends(user_api_key_auth)],
response_model=LiteLLM_MCPServerTable,
status_code=status.HTTP_201_CREATED,
)
@management_endpoint_wrapper
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
@router.delete(
"/server/{server_id}",
description="Allows deleting mcp serves in the db",
dependencies=[Depends(user_api_key_auth)],
response_class=JSONResponse,
status_code=status.HTTP_202_ACCEPTED,
)
@management_endpoint_wrapper
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)
@router.put(
"/server",
description="Allows deleting mcp serves in the db",
dependencies=[Depends(user_api_key_auth)],
response_model=LiteLLM_MCPServerTable,
status_code=status.HTTP_202_ACCEPTED,
)
@management_endpoint_wrapper
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
|