"""MCP Server plugin routes — tool listing, execution, and config.""" from __future__ import annotations import logging import uuid from typing import Any from fastapi import APIRouter, Depends, Header, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_db from app.deps import get_current_user, require_permission from app.plugins.builtins.mcp_server.schemas import ( McpServerConfig, McpToolDefinition, McpToolExecuteRequest, McpToolExecuteResponse, McpToolListResponse, ) from app.plugins.builtins.mcp_server.tool_definitions import ( TOOL_DEFINITIONS, TOOL_HANDLERS, get_all_tool_names, get_tool_definition, ) logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/mcp", tags=["mcp-server"]) async def _get_mcp_context( current_user: dict[str, Any] = Depends(get_current_user), ) -> dict[str, Any]: """Build MCP execution context from authenticated user.""" return { "tenant_id": current_user.get("tenant_id"), "user_id": current_user.get("user_id"), "role": current_user.get("role"), "permissions": current_user.get("permissions", []), } @router.get("/tools", response_model=McpToolListResponse) async def list_mcp_tools( current_user: dict[str, Any] = Depends(require_permission("mcp:read")), ) -> McpToolListResponse: """List all available MCP tools with their schemas.""" return McpToolListResponse( tools=TOOL_DEFINITIONS, count=len(TOOL_DEFINITIONS), ) @router.post("/tools/{tool_name}/execute", response_model=McpToolExecuteResponse) async def execute_mcp_tool( tool_name: str, request: McpToolExecuteRequest, db: AsyncSession = Depends(get_db), current_user: dict[str, Any] = Depends(get_current_user), ) -> McpToolExecuteResponse: """Execute an MCP tool by name with provided arguments. Requires mcp:read for read tools, mcp:write for write tools. """ tool_def = get_tool_definition(tool_name) if not tool_def: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"detail": f"MCP tool '{tool_name}' not found", "code": "tool_not_found"}, ) # Check permission based on tool category required_perm = tool_def.required_permission or "mcp:read" from app.core.permissions import check_permission if not current_user.get("is_system_admin") and not check_permission(current_user, required_perm): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={"detail": f"Permission '{required_perm}' required", "code": "forbidden"}, ) handler = TOOL_HANDLERS.get(tool_name) if not handler: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail={"detail": f"Handler for '{tool_name}' not implemented", "code": "handler_missing"}, ) context = { "tenant_id": current_user.get("tenant_id"), "user_id": current_user.get("user_id"), "role": current_user.get("role"), "permissions": current_user.get("permissions", []), } try: result = await handler(db, request.arguments, context) await db.commit() return McpToolExecuteResponse( tool=tool_name, success="error" not in result, result=result, error=result.get("error"), ) except Exception as exc: logger.exception("MCP tool execution failed: %s", tool_name) await db.rollback() return McpToolExecuteResponse( tool=tool_name, success=False, error=str(exc), ) @router.get("/config", response_model=McpServerConfig) async def get_mcp_config( current_user: dict[str, Any] = Depends(require_permission("mcp:read")), ) -> McpServerConfig: """Get MCP server configuration for external clients.""" return McpServerConfig( server_name="LeoCRM", server_version="1.0.0", protocol_version="2024-11-05", base_url="/api/v1/mcp", auth_method="api-token", available_tools=get_all_tool_names(), )