Files
Agentic-OS/backend/app/api/mcp.py
nearxos 6185b9b85a Initial commit: Agentic OS troubleshooting platform
Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend +
LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini ->
DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials,
RBAC, audit log, project-memory + Obsidian integrations, and editable
troubleshooting decision rules tuned for the GeneseasX vessel stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 22:11:07 +03:00

118 lines
3.9 KiB
Python

"""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.",
)