Spaces:
Sleeping
Sleeping
David Prince commited on
Commit ·
8b4653e
1
Parent(s): ad51ddb
fix: move _servers_with_live_health before @router .get decorator; fix db try block indent
Browse files- app_part1.py +4 -6
- mcp_routes.py +92 -0
app_part1.py
CHANGED
|
@@ -88,15 +88,13 @@ async def init_db_pool() -> asyncpg.Pool:
|
|
| 88 |
async def lifespan(app: FastAPI):
|
| 89 |
global db_pool
|
| 90 |
try:
|
| 91 |
-
try:
|
| 92 |
db_pool = await init_db_pool()
|
| 93 |
logger.info("Database pool initialized.")
|
| 94 |
-
except Exception as _db_err:
|
| 95 |
-
logger.error("Database pool failed: %s", _db_err)
|
| 96 |
-
db_pool = None
|
| 97 |
-
logger.info("Database pool initialized.")
|
| 98 |
except Exception as exc:
|
| 99 |
-
logger.error(
|
|
|
|
|
|
|
|
|
|
| 100 |
db_pool = None
|
| 101 |
yield
|
| 102 |
if db_pool is not None:
|
|
|
|
| 88 |
async def lifespan(app: FastAPI):
|
| 89 |
global db_pool
|
| 90 |
try:
|
|
|
|
| 91 |
db_pool = await init_db_pool()
|
| 92 |
logger.info("Database pool initialized.")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
except Exception as exc:
|
| 94 |
+
logger.error(
|
| 95 |
+
"Failed to initialize Postgres pool. Falling back to memory: %s",
|
| 96 |
+
exc,
|
| 97 |
+
)
|
| 98 |
db_pool = None
|
| 99 |
yield
|
| 100 |
if db_pool is not None:
|
mcp_routes.py
CHANGED
|
@@ -441,7 +441,99 @@ async def dispatch_mcp_jsonrpc(payload: Optional[Dict[str, Any]]) -> Dict[str, A
|
|
| 441 |
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Method not found: {method}"}}
|
| 442 |
|
| 443 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
@router.get("/api/mcp")
|
|
|
|
|
|
|
| 445 |
async def mcp_health() -> JSONResponse:
|
| 446 |
live_servers = _servers_with_live_health()
|
| 447 |
seen: List[str] = []
|
|
|
|
| 441 |
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Method not found: {method}"}}
|
| 442 |
|
| 443 |
|
| 444 |
+
def _servers_with_live_health() -> List[Dict[str, Any]]:
|
| 445 |
+
"""
|
| 446 |
+
Build the MCP server health view from the actual registered runtime
|
| 447 |
+
configuration.
|
| 448 |
+
|
| 449 |
+
This function is deliberately synchronous because mcp_health() invokes
|
| 450 |
+
it directly. It performs no recursive HTTP call against this service,
|
| 451 |
+
avoiding /api/mcp -> health -> /api/mcp recursion.
|
| 452 |
+
|
| 453 |
+
Local builtin MCP infrastructure is healthy when its registered runtime
|
| 454 |
+
contract is present. Remote/stdio servers are reported according to
|
| 455 |
+
their discovered registry state when available.
|
| 456 |
+
"""
|
| 457 |
+
servers: List[Dict[str, Any]] = []
|
| 458 |
+
|
| 459 |
+
# Registered local MCP servers are the authoritative local contract.
|
| 460 |
+
for server in REGISTERED_SERVERS:
|
| 461 |
+
item = dict(server)
|
| 462 |
+
|
| 463 |
+
transport = str(item.get("transport_type", "")).lower()
|
| 464 |
+
endpoint = item.get("endpoint_url")
|
| 465 |
+
|
| 466 |
+
if transport == "http":
|
| 467 |
+
# The builtin HTTP MCP server is part of this running process.
|
| 468 |
+
# Do not probe its own endpoint from inside /api/mcp.
|
| 469 |
+
if item.get("server_id") == "mcp-core-builtin-01":
|
| 470 |
+
status = "healthy"
|
| 471 |
+
health_source = "local_runtime"
|
| 472 |
+
elif endpoint:
|
| 473 |
+
# Remote HTTP endpoints cannot safely be synchronously
|
| 474 |
+
# self-probed here without introducing latency/recursion.
|
| 475 |
+
status = "configured"
|
| 476 |
+
health_source = "registered_endpoint"
|
| 477 |
+
else:
|
| 478 |
+
status = "not_configured"
|
| 479 |
+
health_source = "configuration"
|
| 480 |
+
elif transport == "stdio":
|
| 481 |
+
# stdio servers require an active subprocess/session. Merely
|
| 482 |
+
# having a registry record does not prove that a process exists.
|
| 483 |
+
status = "configured"
|
| 484 |
+
health_source = "registered_transport"
|
| 485 |
+
else:
|
| 486 |
+
status = "configured"
|
| 487 |
+
health_source = "registered_transport"
|
| 488 |
+
|
| 489 |
+
item["health"] = {
|
| 490 |
+
"status": status,
|
| 491 |
+
"healthy": status == "healthy",
|
| 492 |
+
"source": health_source,
|
| 493 |
+
"checked_at": time.time(),
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
servers.append(item)
|
| 497 |
+
|
| 498 |
+
# Include externally registered servers when the registry exposes them.
|
| 499 |
+
# Registry failures must not make the core MCP health endpoint fail.
|
| 500 |
+
try:
|
| 501 |
+
discovered = list_servers()
|
| 502 |
+
if discovered:
|
| 503 |
+
known_ids = {str(x.get("server_id")) for x in servers}
|
| 504 |
+
for remote in discovered:
|
| 505 |
+
if not isinstance(remote, dict):
|
| 506 |
+
continue
|
| 507 |
+
|
| 508 |
+
remote_id = str(
|
| 509 |
+
remote.get("server_id")
|
| 510 |
+
or remote.get("id")
|
| 511 |
+
or remote.get("name")
|
| 512 |
+
or ""
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
if remote_id and remote_id in known_ids:
|
| 516 |
+
continue
|
| 517 |
+
|
| 518 |
+
item = dict(remote)
|
| 519 |
+
item["health"] = {
|
| 520 |
+
"status": "discovered",
|
| 521 |
+
"healthy": False,
|
| 522 |
+
"source": "remote_registry",
|
| 523 |
+
"checked_at": time.time(),
|
| 524 |
+
}
|
| 525 |
+
servers.append(item)
|
| 526 |
+
except Exception:
|
| 527 |
+
# MCP health must remain available even when an optional remote
|
| 528 |
+
# registry is unavailable.
|
| 529 |
+
pass
|
| 530 |
+
|
| 531 |
+
return servers
|
| 532 |
+
|
| 533 |
+
|
| 534 |
@router.get("/api/mcp")
|
| 535 |
+
|
| 536 |
+
|
| 537 |
async def mcp_health() -> JSONResponse:
|
| 538 |
live_servers = _servers_with_live_health()
|
| 539 |
seen: List[str] = []
|