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>
47 lines
1.7 KiB
Python
47 lines
1.7 KiB
Python
"""Auto-reload MCP servers when a tool call fails due to server unavailability."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections.abc import Awaitable, Callable
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
EmitFn = Callable[[int, str, str, dict | None], Awaitable[None]]
|
|
|
|
|
|
async def call_tool_resilient(
|
|
manager,
|
|
server: str,
|
|
tool: str,
|
|
args: dict,
|
|
*,
|
|
task_id: int | None = None,
|
|
emit: EmitFn | None = None,
|
|
max_retries: int = 1,
|
|
) -> dict:
|
|
"""Call MCP tool; on server-unavailable errors, reload that server and retry once."""
|
|
last_exc: Exception | None = None
|
|
for attempt in range(max_retries + 1):
|
|
try:
|
|
return await manager.call_tool(server, tool, args)
|
|
except RuntimeError as exc:
|
|
last_exc = exc
|
|
msg = str(exc).lower()
|
|
recoverable = "not available" in msg or "not loaded" in msg
|
|
if not recoverable or attempt >= max_retries:
|
|
raise
|
|
logger.warning("MCP %s unavailable for %s — reloading (attempt %s)", server, tool, attempt + 1)
|
|
if task_id and emit:
|
|
await emit(
|
|
task_id,
|
|
"log",
|
|
f"MCP '{server}' unavailable — reloading server and retrying {tool}…",
|
|
{"server": server, "tool": tool},
|
|
)
|
|
try:
|
|
await manager.reload_server(server)
|
|
except Exception as reload_exc: # noqa: BLE001
|
|
logger.warning("MCP reload %s failed: %s", server, reload_exc)
|
|
raise exc from reload_exc
|
|
raise last_exc or RuntimeError(f"MCP call failed: {server}/{tool}")
|