"""MCP server status panel and maintenance.""" from __future__ import annotations import json from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import require_admin, require_readonly from app.database import get_db from app.mcp_manager.commands import enqueue_mcp_command, enqueue_mcp_command_async from app.mcp_manager.manager import MCP_STATUS_KEY from app.models.enums import TaskStatus from app.models.task import Task from app.models.user import User from app.schemas import McpCommandResult, McpDevelopRequest, McpDevelopResult, McpServerOut from app.services.audit import record_audit from app.services.events import get_redis, publish_event router = APIRouter(prefix="/api/mcp", tags=["mcp"]) @router.get("/servers", response_model=list[McpServerOut]) async def list_mcp_servers(_: User = Depends(require_readonly)): raw = await get_redis().get(MCP_STATUS_KEY) if not raw: return [] data = json.loads(raw) return [McpServerOut(**s) for s in data] @router.post("/reload-all", response_model=McpCommandResult) async def reload_all_mcps(_: User = Depends(require_admin)): result = await enqueue_mcp_command("reload_all") if not result.get("ok"): raise HTTPException(status_code=502, detail=result.get("error", "Reload failed")) return McpCommandResult(**result) @router.post("/servers/{name}/reload", response_model=McpCommandResult) async def reload_mcp_server(name: str, _: User = Depends(require_admin)): result = await enqueue_mcp_command("reload", server=name) if not result.get("ok"): raise HTTPException(status_code=502, detail=result.get("error", "Reload failed")) return McpCommandResult(**result) @router.post("/servers/{name}/upgrade", response_model=McpCommandResult) async def upgrade_mcp_server(name: str, _: User = Depends(require_admin)): """Git pull (or re-sync local copy) and restart the MCP server.""" result = await enqueue_mcp_command("upgrade", server=name) if not result.get("ok"): raise HTTPException(status_code=502, detail=result.get("error", "Upgrade failed")) return McpCommandResult(**result) @router.post("/servers/{name}/develop", response_model=McpDevelopResult) async def develop_mcp_server( name: str, body: McpDevelopRequest, db: AsyncSession = Depends(get_db), user: User = Depends(require_admin), ): """Research and patch an MCP server; progress is streamed on a monitor task.""" task_id = body.task_id if task_id: res = await db.execute(select(Task).where(Task.id == task_id)) task = res.scalar_one_or_none() if not task: raise HTTPException(status_code=404, detail="Task not found") task.status = TaskStatus.running task.error = None await db.commit() else: task = Task( title=f"MCP develop: {name}", issue=body.issue.strip(), details={"kind": "mcp_develop", "mcp_server": name, "tool": body.tool}, status=TaskStatus.running, created_by_id=user.id, ) db.add(task) await db.commit() await db.refresh(task) task_id = task.id await publish_event( task_id, "log", f"MCP develop queued for {name}", {"server": name, "kind": "mcp_develop"}, persist=True, ) await enqueue_mcp_command_async( "develop", server=name, issue=body.issue.strip(), tool=body.tool, task_id=task_id, push=body.push, title=f"MCP develop: {name}", ) await record_audit( db, user, "mcp.develop", "mcp", task_id, {"server": name, "issue": body.issue[:500]}, ) return McpDevelopResult( ok=True, task_id=task_id, summary=f"Development started — open task #{task_id} to watch live progress.", )