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>
4.0 KiB
4.0 KiB
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
MCPToolStatsdataclass:calls: int = 0,errors: int = 0,total_duration_ms: float = 0.0,last_called_at: datetime | None = None;avg_duration_msproperty returningNonewhencalls == 0.MCPStatsTrackerwiththreading.Lock,record(tool, duration_ms, success),snapshot()returning a shallow copy.record()wraps its body intry/except Exception→logger.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 == 800exactly. avg_duration_msisNoneat zero calls, correct mean afterwards.success=Falseincrementserrorsandcalls.record()with a non-numeric duration logs and does not raise.
Task 3 — instrument app/mcp/server.py
- Wrap
search_regulationsbody:time.perf_counter()start,try/exceptrecordssuccess=Falseand re-raises,finallynot needed once both branches record. async def get_mcp_status(public_url: str) -> dictreturning{endpoint_url, auth_required, allowed_hosts, tools: [...]}where each tool is{name, description, calls, errors, avg_duration_ms, last_called_at}.allowed_hostsparsed fromsettings.mcp_allowed_hostswith the same split/strip logic_build_transport_security()already uses.last_called_atserialized as ISO-8601 string orNone.
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 thehttp://6.86.80.9:8000/mcp/example and a note that it is only needed when a proxy rewritesHost.
Task 5 — GET /status/mcp
- Add route to
backend/app/api/routes/status.py, takingrequest: 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_requiredfollows a patchedsettings.auth_enabled.public_urlpasses 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 existingPromise.allSettledbatch inStatusPage.tsx, with its ownmcpLoadingstate. - Card below "AI Models": endpoint row + one row per tool.
- Copy-config button: builds the
mcpServersJSON, embeds thelocalStoragetoken whenauth_required, writes vianavigator.clipboard.writeText, and reflects success/failure in its label for ~2s. handleExport()includesmcp.- Reuse
card/card-header/service-row/StatusIcon. No new CSS.
Task 9 — i18n
locales/zh.tsandlocales/en.ts:cardMcp,mcpEndpoint,mcpAuthRequired,mcpAuthDisabled,mcpAllowedHosts,mcpCopyConfig,mcpCopied,mcpCopyFailed,mcpCalls,mcpErrors,mcpAvgDuration,mcpNoTools,mcpUnavailable.- Both files must stay structurally identical (
en.tsis typed againstzh.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 MCPtools/call.