Files
AIRegulation-DocAnalysis/docs/superpowers/plans/2026-08-03-mcp-status-panel.md
T
wangweiandCopilot 31bbf80aeb feat: surface MCP server status in System Status page
Add per-tool in-memory call counters to the MCP module and a
GET /api/v1/status/mcp endpoint that joins them with the live tool
registry and endpoint config, then render it as a new card on the
System Status page with a one-click client-config copy button.

- app/mcp/stats.py: lock-guarded MCPStatsTracker (the mcp SDK runs sync
  tool bodies via anyio.to_thread.run_sync, so this is genuinely
  multi-threaded, unlike the async REST routes)
- app/mcp/server.py: instrument search_regulations, add get_mcp_status()
- app/config/settings.py: optional MCP_PUBLIC_URL override, required
  because the Vite proxy and reverse proxies rewrite the Host header
- StatusPage.tsx: MCP Server card, joins the existing parallel fetch

Counters are process-local by design; token usage is already persisted
by ModelUsageTracker since MCP calls route through ask().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-03 11:37:22 +08:00

4.0 KiB
Raw Blame History

MCP Status Panel — Implementation Plan

Spec: docs/superpowers/specs/2026-08-03-mcp-status-panel-design.md

Backend first (tests alongside), then frontend, then verify. Each task is independently reviewable.

Task 1 — app/mcp/stats.py

  • MCPToolStats dataclass: calls: int = 0, errors: int = 0, total_duration_ms: float = 0.0, last_called_at: datetime | None = None; avg_duration_ms property returning None when calls == 0.
  • MCPStatsTracker with threading.Lock, record(tool, duration_ms, success), snapshot() returning a shallow copy.
  • record() wraps its body in try/except Exceptionlogger.warning, never raises.
  • get_mcp_stats_tracker() with @lru_cache.
  • Module docstring + at least one # comment (AGENTS.md).

Task 2 — backend/tests/mcp/test_mcp_stats.py

  • 8 threads × 100 record() calls → calls == 800 exactly.
  • avg_duration_ms is None at zero calls, correct mean afterwards.
  • success=False increments errors and calls.
  • record() with a non-numeric duration logs and does not raise.

Task 3 — instrument app/mcp/server.py

  • Wrap search_regulations body: time.perf_counter() start, try/except records success=False and re-raises, finally not needed once both branches record.
  • async def get_mcp_status(public_url: str) -> dict returning {endpoint_url, auth_required, allowed_hosts, tools: [...]} where each tool is {name, description, calls, errors, avg_duration_ms, last_called_at}.
  • allowed_hosts parsed from settings.mcp_allowed_hosts with the same split/strip logic _build_transport_security() already uses.
  • last_called_at serialized as ISO-8601 string or None.

Task 4 — mcp_public_url setting

  • app/config/settings.py: mcp_public_url: str = "" in the existing # ── MCP ── block.
  • .env.example: documented under the existing MCP section, in Chinese, with the http://6.86.80.9:8000/mcp/ example and a note that it is only needed when a proxy rewrites Host.

Task 5 — GET /status/mcp

  • Add route to backend/app/api/routes/status.py, taking request: Request.
  • public_url = settings.mcp_public_url or f"{str(request.base_url).rstrip('/')}/mcp/".
  • Delegate to get_mcp_status(); no MCP internals in the route.

Task 6 — backend/tests/mcp/test_mcp_status.py

  • Tool advertised with zeroed stats before any call.
  • Stats reflected after record().
  • auth_required follows a patched settings.auth_enabled.
  • public_url passes through unmodified.

Task 7 — frontend types + client

  • frontend/src/api/index.ts: MCPToolEntry, MCPStatusResponse.
  • frontend/src/api/status.ts: getMCPStatus() + re-export.

Task 8 — MCP Server card

  • Add getMCPStatus() to the existing Promise.allSettled batch in StatusPage.tsx, with its own mcpLoading state.
  • Card below "AI Models": endpoint row + one row per tool.
  • Copy-config button: builds the mcpServers JSON, embeds the localStorage token when auth_required, writes via navigator.clipboard.writeText, and reflects success/failure in its label for ~2s.
  • handleExport() includes mcp.
  • Reuse card / card-header / service-row / StatusIcon. No new CSS.

Task 9 — i18n

  • locales/zh.ts and locales/en.ts: cardMcp, mcpEndpoint, mcpAuthRequired, mcpAuthDisabled, mcpAllowedHosts, mcpCopyConfig, mcpCopied, mcpCopyFailed, mcpCalls, mcpErrors, mcpAvgDuration, mcpNoTools, mcpUnavailable.
  • Both files must stay structurally identical (en.ts is typed against zh.ts).

Task 10 — verify

  • python -m pytest backend/tests -q — all pass.
  • npm --prefix frontend run lint.
  • npm --prefix frontend run build.
  • Live check: start uvicorn, GET /api/v1/status/mcp, confirm the tool is listed and counters move after a real MCP tools/call.