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
Files changed (2) hide show
  1. app_part1.py +4 -6
  2. 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("Failed to initialize Postgres pool. Falling back to memory: %s", exc)
 
 
 
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] = []