From 3a68eacd9dd9c0fbc6bf1e0b7ee4f9893d1be77b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 01:18:30 +0000 Subject: [PATCH 1/2] Add provider status badges and registered tools list panel to web UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New GET /api/provider-status endpoint exposes per-provider initialization state (pending/ready/failed) from the background-startup registry - Provider list now shows "⏳ initializing" badge while setup runs and "βœ— setup failed" badge (with error tooltip) if setup fails; ready providers show no status badge. Badges update every 4 seconds and are removed automatically once a provider becomes ready, no matter how long initialization takes. - New "πŸ“‹ Tools" navbar button opens a read-only modal listing every registered tool grouped by provider, with status badges (⏳/βœ“/βœ—) and tool counts on each section header; includes a name/description filter - README documents the new badges, Tools panel, and /api/provider-status endpoint including the status schema - 10 new unit tests (8 functional + 2 UI smoke) covering all status states and the new UI elements; all 204 tests pass Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01Xy6caDMbbavMB1uzAP64jN --- README.md | 51 ++++++++++++ frontend/app.py | 174 +++++++++++++++++++++++++++++++++++++++-- tests/test_frontend.py | 86 ++++++++++++++++++++ 3 files changed, 306 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1d03dbd..92577e4 100755 --- a/README.md +++ b/README.md @@ -214,6 +214,57 @@ All paths are validated against the whitelisted roots (directory-traversal and symlink-escape attempts are rejected), and uploads stream to disk with a size cap (`MCPPROXY_MAX_UPLOAD_BYTES`, default 50 MB). +### Provider status badges + +While background setup is running, the left-panel provider list shows live badges: + +| Badge | Meaning | +|---|---| +| **⏳ initializing** (yellow) | Provider's dependencies are still installing β€” tools are advertised but return a retry directive until setup finishes. Badge disappears automatically once the provider is ready, however long that takes. | +| **βœ— setup failed** (red, with tooltip) | An actual error occurred during setup β€” hover the badge to read the error. All other providers are unaffected. | +| *(no status badge)* | Provider is ready. | + +The badges are updated every 4 seconds via `GET /api/provider-status`. A provider stays +marked **⏳ initializing** for as long as it needs β€” it is never changed to "failed" because +of timing alone; only an actual exception during setup produces the red badge. + +### Registered tools list + +The **πŸ“‹ Tools** navbar button opens a read-only panel showing every tool currently +exposed by the proxy, grouped by provider. Each provider section shows: + +- A status badge (⏳ initializing / βœ“ ready / βœ— failed) +- The number of tools it contributes +- Each tool's short name and description + +A filter box narrows results by tool name or description. This panel is useful for a +quick audit of what the LLM can currently see, especially during startup when some +providers may still be initializing. + +Under the hood it reads the same `GET /v1/tools` endpoint as the tool tester, plus +`GET /api/provider-status` for the readiness information. + +``` +GET /api/provider-status +``` + +Returns per-provider initialization state: + +```json +{ + "ok": true, + "providers": { + "playwright": {"status": "pending", "error": null}, + "github": {"status": "ready", "error": null}, + "myflaky": {"status": "failed", "error": "pip install returned exit code 1 …"} + } +} +``` + +`status` is one of `"pending"` (still installing), `"ready"` (setup complete), or +`"failed"` (setup threw an error). Returns `{"ok": true, "providers": {}}` when +`MCPPROXY_BACKGROUND_SETUP=0` (synchronous mode, nothing to track). + ### Tool tester The **πŸ§ͺ Test Tools** navbar button lists every registered tool, grouped by provider, diff --git a/frontend/app.py b/frontend/app.py index 0820319..5bfc968 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -22,6 +22,7 @@ POST /api/oauth-bootstrap β€” begin a provider-declared OAuth consent flow {name} POST /api/restart β€” send SIGTERM to restart server GET /api/config β€” UI feature flags (e.g. web_terminal) +GET /api/provider-status β€” per-provider init state {providers: {name: {status, error}}} WS /ws/terminal β€” interactive PTY terminal (optional ?cmd=…) """ @@ -1358,6 +1359,30 @@ async def client_config() -> dict: """Expose UI feature flags so the front end can hide disabled features.""" return {"ok": True, "web_terminal": _web_terminal_enabled()} + @app.get("/api/provider-status") + async def provider_status_api() -> dict: + """Return per-provider initialization status from the background startup. + + Each entry maps a provider name to its current readiness state: + ``status`` is ``"pending"`` (still installing), ``"ready"`` (setup done), + or ``"failed"`` (setup threw an error). ``error`` is ``null`` unless + ``status == "failed"``, in which case it holds the failure message. + + Returns ``{"ok": True, "providers": {}}`` when the background-startup + module is not loaded (e.g. ``MCPPROXY_BACKGROUND_SETUP=0``). + """ + try: + import provider_status as _ps # same in-process dict server.py populates + return { + "ok": True, + "providers": { + name: {"status": s.status, "error": s.error} + for name, s in _ps.all_states().items() + }, + } + except ImportError: + return {"ok": True, "providers": {}} + # ── Interactive web terminal (PTY over WebSocket) ────────────────────────── @app.websocket("/ws/terminal") @@ -1541,6 +1566,9 @@ async def index(): .badge-warn{background:var(--yellow);color:#1e1e2e;font-size:.62em;padding:2px 6px;border-radius:3px;font-weight:700} .badge-err{background:var(--red);color:#1e1e2e;font-size:.62em;padding:2px 6px;border-radius:3px;font-weight:700} .badge-disabled{background:#45475a;color:var(--muted);font-size:.62em;padding:2px 6px;border-radius:3px;font-weight:700;text-transform:uppercase;letter-spacing:.4px} +.badge-status-pending{background:var(--yellow);color:#1e1e2e;font-size:.62em;padding:2px 6px;border-radius:3px;font-weight:700} +.badge-status-ready{background:var(--green);color:#1e1e2e;font-size:.62em;padding:2px 6px;border-radius:3px;font-weight:700} +.badge-status-failed{background:var(--red);color:#1e1e2e;font-size:.62em;padding:2px 6px;border-radius:3px;font-weight:700} .tool-card.disabled .tool-card-body{opacity:.55;filter:saturate(.6)} .tool-card.disabled .tool-card-header{background:#1f1f2c} .fn-pick-row{display:flex;gap:6px;align-items:stretch} @@ -1567,6 +1595,8 @@ async def index(): onclick="openTerminal()" title="Open an interactive shell in the server container">πŸ–₯ Terminal + @@ -2202,6 +2232,23 @@ async def index(): + + +