Files
Agentic-OS/backend/app/mcp_manager/commands.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

192 lines
6.5 KiB
Python

"""Redis queue for MCP maintenance commands (worker executes reload/upgrade/develop)."""
from __future__ import annotations
import asyncio
import json
import uuid
from collections.abc import Awaitable, Callable
from typing import Any
from app.services.events import get_redis
MCP_COMMAND_QUEUE = "agentic:mcp:commands"
def _result_key(request_id: str) -> str:
return f"agentic:mcp:cmd_result:{request_id}"
async def _wait_result(request_id: str, timeout_seconds: float) -> dict:
redis = get_redis()
deadline = asyncio.get_event_loop().time() + timeout_seconds
while asyncio.get_event_loop().time() < deadline:
raw = await redis.get(_result_key(request_id))
if raw:
await redis.delete(_result_key(request_id))
return json.loads(raw)
await asyncio.sleep(0.5)
return {"ok": False, "error": "Worker did not respond in time — is the worker running?"}
async def enqueue_mcp_command(
action: str,
*,
server: str | None = None,
timeout_seconds: float = 90,
**extra: Any,
) -> dict:
request_id = str(uuid.uuid4())
cmd = {"request_id": request_id, "action": action, "server": server, **extra}
await get_redis().rpush(MCP_COMMAND_QUEUE, json.dumps(cmd))
return await _wait_result(request_id, timeout_seconds)
async def enqueue_mcp_command_async(
action: str,
*,
server: str | None = None,
**extra: Any,
) -> str:
"""Queue a command for the worker without waiting for the result."""
request_id = str(uuid.uuid4())
cmd = {"request_id": request_id, "action": action, "server": server, **extra}
await get_redis().rpush(MCP_COMMAND_QUEUE, json.dumps(cmd))
return request_id
async def store_mcp_command_result(request_id: str, result: dict) -> None:
redis = get_redis()
await redis.set(_result_key(request_id), json.dumps(result), ex=300)
async def process_mcp_command(cmd: dict) -> dict:
from app.mcp_manager.manager import get_manager
action = cmd.get("action")
server = cmd.get("server")
manager = get_manager()
try:
if action == "reload":
if not server:
raise ValueError("server name required")
await manager.reload_server(server)
elif action == "upgrade":
if not server:
raise ValueError("server name required")
await manager.upgrade_server(server)
elif action == "reload_all":
await manager.reload_all()
elif action == "develop":
if not server:
raise ValueError("server name required")
return await _execute_develop_command(cmd, manager)
else:
raise ValueError(f"Unknown action: {action}")
return {"ok": True, "action": action, "server": server}
except Exception as exc: # noqa: BLE001
return {"ok": False, "action": action, "server": server, "error": str(exc)}
async def _execute_develop_command(cmd: dict, manager) -> dict:
server = cmd["server"]
result = await _develop_on_worker(cmd, manager)
task_id = int(cmd.get("task_id") or 0)
if task_id:
await _finalize_develop_task(task_id, result, server)
return result
async def run_develop_command_background(cmd: dict, manager) -> None:
"""Run MCP develop without blocking the worker task queue loop."""
request_id = cmd.get("request_id", "")
try:
result = await _execute_develop_command(cmd, manager)
await store_mcp_command_result(request_id, result)
except Exception as exc: # noqa: BLE001
await store_mcp_command_result(
request_id,
{"ok": False, "action": "develop", "server": cmd.get("server"), "error": str(exc)},
)
async def _finalize_develop_task(task_id: int, result: dict, server: str) -> None:
from sqlalchemy import select
from app.database import SessionLocal
from app.models.enums import ApprovalStatus, TaskStatus
from app.models.task import ApprovalRequest, Task
from app.services.events import publish_event
async with SessionLocal() as db:
res = await db.execute(select(Task).where(Task.id == task_id))
task = res.scalar_one_or_none()
if not task:
return
pend = await db.execute(
select(ApprovalRequest).where(
ApprovalRequest.task_id == task_id,
ApprovalRequest.status == ApprovalStatus.pending,
)
)
has_pending = pend.scalars().first() is not None
if result.get("ok"):
task.report = {
"kind": "mcp_develop",
"mcp_server": server,
"summary": result.get("summary"),
"tools": result.get("tools"),
"push": result.get("push"),
"resolved": not has_pending,
}
task.error = None
task.status = TaskStatus.waiting_approval if has_pending else TaskStatus.succeeded
else:
task.status = TaskStatus.failed
task.error = result.get("error") or result.get("reason") or "MCP development failed"
await db.commit()
if result.get("ok"):
msg = result.get("summary") or f"MCP develop finished for {server}"
if has_pending:
msg += " — approve git push on this task."
await publish_event(task_id, "report", msg, {"kind": "mcp_develop", **result}, persist=True)
else:
err = result.get("error") or result.get("reason") or "MCP development failed"
await publish_event(task_id, "error", err, result, persist=True)
async def _develop_on_worker(cmd: dict, manager) -> dict:
from app.agent.mcp_development import attempt_mcp_repair
from app.services.events import publish_event
server = cmd["server"]
task_id = int(cmd.get("task_id") or 0)
async def emit(tid: int, kind: str, message: str, payload: dict | None = None):
if tid:
await publish_event(tid, kind, message, payload)
if task_id:
await emit(
task_id,
"log",
f"MCP develop started for {server}",
{"server": server, "kind": "mcp_develop"},
)
return await attempt_mcp_repair(
task_id=task_id,
server=server,
tool=cmd.get("tool") or "unknown",
args={},
issue=cmd.get("issue") or "",
error=cmd.get("error"),
output=cmd.get("output"),
manager=manager,
emit=emit,
task_title=cmd.get("title") or f"MCP develop: {server}",
push=cmd.get("push"),
)