diff --git a/SPECS/ARCHIVE/INDEX.md b/SPECS/ARCHIVE/INDEX.md index 836670bc..fee60df8 100644 --- a/SPECS/ARCHIVE/INDEX.md +++ b/SPECS/ARCHIVE/INDEX.md @@ -1,11 +1,12 @@ # mcpbridge-wrapper Tasks Archive -**Last Updated:** 2026-03-07 (P1-T12 archived) +**Last Updated:** 2026-03-07 (REVIEW_broker_runtime_status_surface archived) ## Archived Tasks | Task ID | Folder | Archived | Verdict | |---------|--------|----------|---------| +| P6-T1 | [P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/](P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/) | 2026-03-07 | PASS | | P1-T12 | [P1-T12_Improve_troubleshooting_docs_for_Zed_broker_startup_timeouts/](P1-T12_Improve_troubleshooting_docs_for_Zed_broker_startup_timeouts/) | 2026-03-07 | PASS | | P5-T2 | [P5-T2_Release_0.4.1_to_PyPI_and_MCP_Registry/](P5-T2_Release_0.4.1_to_PyPI_and_MCP_Registry/) | 2026-03-06 | PASS | | BUG-T9 | [BUG-T9_Fix_broker_daemon_not_sending_notifications_initialized_before_tools_list_probe/](BUG-T9_Fix_broker_daemon_not_sending_notifications_initialized_before_tools_list_probe/) | 2026-03-06 | PASS | @@ -193,6 +194,7 @@ | File | Description | |------|-------------| +| [REVIEW_broker_runtime_status_surface.md](_Historical/REVIEW_broker_runtime_status_surface.md) | Review report for P6-T1 | | [REVIEW_p1_t12_zed_timeout_docs.md](_Historical/REVIEW_p1_t12_zed_timeout_docs.md) | Review report for P1-T12 | | [REVIEW_P4-T2_broker_readiness_cache.md](_Historical/REVIEW_P4-T2_broker_readiness_cache.md) | Review report for P4-T2 | | [REVIEW_P1-T11_readme_coverage_badge.md](_Historical/REVIEW_P1-T11_readme_coverage_badge.md) | Review report for P1-T11 | @@ -329,6 +331,8 @@ | Date | Task ID | Action | |------|---------|--------| +| 2026-03-07 | P6-T1 | Archived REVIEW_broker_runtime_status_surface report | +| 2026-03-07 | P6-T1 | Archived task artifacts and validation report | | 2026-03-07 | P1-T12 | Archived task artifacts and validation report | | 2026-03-06 | P5-T2 | Archived Release_0.4.1_to_PyPI_and_MCP_Registry (PASS) | | 2026-03-06 | P5-T2 | Archived REVIEW_release-0.4.1 report | diff --git a/SPECS/ARCHIVE/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers.md b/SPECS/ARCHIVE/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers.md new file mode 100644 index 00000000..71ef5f79 --- /dev/null +++ b/SPECS/ARCHIVE/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers.md @@ -0,0 +1,119 @@ +# P6-T1 — Add explicit broker runtime status surface for frontend consumers + +## Objective Summary + +The broker already has internal lifecycle state and the Web UI already exposes +basic control metadata, but operators still have to infer real daemon health +from PID files, `--broker-status`, and raw log lines. This task adds one +explicit runtime status surface that frontend consumers can trust. The result +must make it obvious whether the dedicated broker host is healthy, reconnecting, +missing an upstream process, or serving active client sessions. + +This task does not implement the TUI itself. It delivers the structured runtime +contract that a TUI or other explicit frontend can consume without log parsing. + +## Deliverables + +- Extend broker runtime status payloads to include operator-facing fields beyond + the current `state` / `pid` / `upstream_pid` / `version` tuple. +- Expose that payload through the Web UI server in a dedicated API route rather + than overloading `/api/control`. +- Cover ready, degraded, and reconnecting status states with automated tests. + +## Success Criteria + +- A dedicated broker host exposes structured status including broker state, + daemon PID, upstream PID when present, version, and connected client count. +- The payload clearly distinguishes healthy vs reconnecting / not-ready states. +- Frontend consumers can detect whether upstream initialization has completed + without reading `broker.log`. +- Existing control endpoints remain backward-compatible. + +## Test-First Plan + +1. Add Web UI server tests for a new broker-status endpoint and assert the JSON + schema in both default/no-runtime and broker-runtime-backed modes. +2. Add or extend broker daemon tests for richer `status()` payloads, including + connected session count and readiness indicators. +3. Implement the production changes only after the expected payload shape is + fixed in tests. +4. Run full quality gates after implementation: `pytest`, `ruff check src/`, + `mypy src/`, and `pytest --cov`. + +## Execution Plan + +### Phase 1: Define the runtime contract + +Inputs: +- Existing `BrokerDaemon.status()` behavior +- Existing `/api/control`, `/api/config`, and `/api/sessions` routes + +Outputs: +- Final status schema for frontend consumers +- Decision on how broker runtime is injected into the Web UI app + +Verification: +- The schema is stable enough to power a future TUI without additional parsing +- Backward-compatible control API behavior is preserved + +### Phase 2: Implement daemon + Web UI wiring + +Inputs: +- `src/mcpbridge_wrapper/broker/daemon.py` +- `src/mcpbridge_wrapper/webui/server.py` +- broker-daemon startup path in `src/mcpbridge_wrapper/__main__.py` + +Outputs: +- Enriched daemon status payload +- Dedicated Web UI broker-status route +- Broker daemon startup passes a runtime status provider/callback into the UI + +Verification: +- A running dedicated host returns live daemon/runtime details via HTTP +- Non-broker or dashboard-only runtimes fail gracefully with a clear empty or + unavailable status response instead of crashing + +### Phase 3: Lock the behavior with tests and validation + +Inputs: +- New endpoint implementation +- Existing server and broker daemon test suites + +Outputs: +- Unit tests for the endpoint and status payload +- Validation report with quality gate results + +Verification: +- Tests cover healthy and degraded states +- Coverage remains at or above project threshold + +## Acceptance Tests + +- `pytest tests/unit/test_broker_daemon.py -k status` +- `pytest tests/unit/webui/test_server.py -k broker` +- `pytest` +- `ruff check src/` +- `mypy src/` +- `pytest --cov` + +## Decision Points + +- The status endpoint should report runtime data through a dedicated API route + such as `/api/broker/status`, not by mutating `/api/control`, because control + and observability are separate concerns. +- Session count should come from the transport/runtime object when available so + the value reflects live attached clients rather than historical audit sessions. +- Readiness should be explicit. A frontend needs to know whether the daemon is + merely alive or whether upstream initialization is complete. + +## Notes + +- If the Web UI needs a small refactor to accept an optional runtime status + provider, keep that refactor scoped and test-covered. +- Docs updates for using this new surface belong in `P6-T3`, unless a tiny + endpoint mention is required inline for immediate correctness. +- Review subject name for this task: `broker_runtime_status_surface`. + +--- +**Archived:** 2026-03-07 +**Verdict:** PASS diff --git a/SPECS/ARCHIVE/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/P6-T1_Validation_Report.md b/SPECS/ARCHIVE/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/P6-T1_Validation_Report.md new file mode 100644 index 00000000..678d935f --- /dev/null +++ b/SPECS/ARCHIVE/P6-T1_Add_explicit_broker_runtime_status_surface_for_frontend_consumers/P6-T1_Validation_Report.md @@ -0,0 +1,76 @@ +# P6-T1 Validation Report + +**Task:** P6-T1 — Add explicit broker runtime status surface for frontend consumers +**Date:** 2026-03-07 +**Verdict:** PASS + +## Summary + +Implemented a dedicated broker runtime status surface for frontend consumers by: + +- enriching `BrokerDaemon.status()` with operator-facing fields +- exposing `GET /api/broker/status` in the Web UI server +- wiring a broker-status provider into broker-daemon Web UI startup +- extending unit tests for daemon status, Web UI status API, and main wiring + +## Files Validated + +- `src/mcpbridge_wrapper/broker/daemon.py` +- `src/mcpbridge_wrapper/webui/server.py` +- `src/mcpbridge_wrapper/__main__.py` +- `tests/unit/test_broker_daemon.py` +- `tests/unit/webui/test_server.py` +- `tests/unit/test_main.py` + +## Targeted Verification + +```bash +PYTHONPATH=src pytest tests/unit/test_broker_daemon.py -k status +``` + +- Result: `3 passed` + +```bash +PYTHONPATH=src pytest tests/unit/webui/test_server.py -k 'broker_status or control' +``` + +- Result: `6 passed` + +```bash +PYTHONPATH=src pytest tests/unit/test_main.py -k 'broker_daemon_webui' +``` + +- Result: `3 passed` + +## Required Quality Gates + +```bash +PYTHONPATH=src pytest +``` + +- Result: `787 passed, 5 skipped in 8.02s` + +```bash +ruff check src/ +``` + +- Result: `All checks passed!` + +```bash +mypy src/ +``` + +- Result: `Success: no issues found in 18 source files` + +```bash +PYTHONPATH=src pytest --cov +``` + +- Result: `787 passed, 5 skipped in 8.94s` +- Coverage: `90.64%` + +## Notes + +- `PYTHONPATH=src` was required for `pytest` in the current local shell because the package is not installed into that interpreter environment. +- Coverage remains above the repository threshold of `90%`. +- Remaining warnings are pre-existing dependency deprecations from `websockets`/`uvicorn`, not regressions introduced by this task. diff --git a/SPECS/ARCHIVE/_Historical/REVIEW_broker_runtime_status_surface.md b/SPECS/ARCHIVE/_Historical/REVIEW_broker_runtime_status_surface.md new file mode 100644 index 00000000..9625615f --- /dev/null +++ b/SPECS/ARCHIVE/_Historical/REVIEW_broker_runtime_status_surface.md @@ -0,0 +1,47 @@ +## REVIEW REPORT — Broker Runtime Status Surface + +**Scope:** `origin/main..HEAD` +**Files:** 11 + +### Summary Verdict +- [x] Approve +- [ ] Approve with comments +- [ ] Request changes +- [ ] Block + +### Critical Issues + +None. + +### Secondary Issues + +None. + +### Architectural Notes + +- The new broker runtime status surface is correctly separated from control + concerns by adding `GET /api/broker/status` instead of mutating + `/api/control`. +- `BrokerDaemon.status()` now exposes enough operator-facing state for an + explicit frontend to distinguish healthy, reconnecting, and not-ready broker + states without reading pid files or parsing logs. +- Broker-daemon Web UI startup keeps the new status surface optional, so + non-broker and dashboard-only runtimes remain backward-compatible. + +### Tests + +- Validation report confirms: + - `PYTHONPATH=src pytest` -> `787 passed, 5 skipped` + - `ruff check src/` -> pass + - `mypy src/` -> pass + - `PYTHONPATH=src pytest --cov` -> `90.64%` +- Targeted status-path tests cover daemon status payloads, Web UI endpoint + responses, and broker-daemon wiring in `__main__.py`. +- Re-ran focused review checks on this branch: + - `PYTHONPATH=src pytest tests/unit/test_broker_daemon.py -k status -q` -> `3 passed` + - `PYTHONPATH=src pytest tests/unit/webui/test_server.py -k broker_status -q` -> `2 passed` + +### Next Steps + +- FOLLOW-UP skipped: no actionable review findings. +- Proceed to `ARCHIVE-REVIEW` and start planning `P6-T2`. diff --git a/SPECS/INPROGRESS/next.md b/SPECS/INPROGRESS/next.md index ab5526b7..2b31432b 100644 --- a/SPECS/INPROGRESS/next.md +++ b/SPECS/INPROGRESS/next.md @@ -1,19 +1,18 @@ -# Next Task: None — all tracked tasks completed +# Next Task: P6-T2 — Build a terminal frontend for broker daemon monitoring and control -**Priority:** — -**Phase:** — -**Effort:** — -**Dependencies:** — -**Status:** All done +**Priority:** P1 +**Phase:** Phase 6: Explicit Broker Frontend +**Dependencies:** P6-T1 +**Status:** Ready + +## Description + +Implement a terminal-first operator interface for the broker daemon so users can explicitly see whether the daemon is running, whether upstream Xcode connectivity is healthy, which clients are attached, and what recent reconnect/error events occurred. The interface should give a clearer operational model than auto-spawn alone. ## Recently Archived -- **P1-T12** (2026-03-07): Improve troubleshooting docs for Zed broker startup timeouts — PASS -- **P5-T2** (2026-03-06): Release 0.4.1 to PyPI and MCP Registry — PASS -- **BUG-T9** (2026-03-06): Fix broker daemon not sending notifications/initialized before tools/list probe — PASS -- **P5-T1** (2026-03-06): Release 0.4.0 to PyPI and MCP Registry — PASS +- `P6-T1` — Add explicit broker runtime status surface for frontend consumers (`PASS`, archived 2026-03-07) -## Description +## Next Step -All tasks in `SPECS/Workplan.md` have been completed. No next task is available. -To add new work, use the `workplan-task-ops` skill or edit `SPECS/Workplan.md` directly. +Run the PLAN command for `P6-T2` and define the TUI scope, entrypoint, and control flow against the new broker runtime status API. diff --git a/SPECS/Workplan.md b/SPECS/Workplan.md index 98b0b292..8917c965 100644 --- a/SPECS/Workplan.md +++ b/SPECS/Workplan.md @@ -419,3 +419,49 @@ Add new tasks using the canonical template in [TASK_TEMPLATE.md](TASK_TEMPLATE.m - [ ] MCP Registry entry reflects `0.4.0` (auto-triggered by tag push via CI/CD) - [ ] README version badge displays `v0.4.0` after PyPI publish propagates (auto after tag push) - [x] All quality gates pass on the tagged commit (`pytest` 785 tests, 90.91% coverage, `ruff`, `mypy`, DocC sync, package assets check) + +### Phase 6: Explicit Broker Frontend + +#### ✅ P6-T1: Add explicit broker runtime status surface for frontend consumers +- **Status:** ✅ Completed (2026-03-07) +- **Description:** Add a structured runtime status surface for the persistent broker so explicit frontends do not need to infer daemon health from pid files and log parsing alone. The surface should expose broker lifecycle state, upstream pid/availability, client session counts, and other operator-facing details that explain whether the daemon is healthy, reconnecting, or awaiting approval. +- **Priority:** P1 +- **Dependencies:** none +- **Parallelizable:** yes +- **Outputs/Artifacts:** + - `src/mcpbridge_wrapper/broker/daemon.py` runtime status payload extended with readiness, upstream-health, and connected-client details + - `src/mcpbridge_wrapper/webui/server.py` new `GET /api/broker/status` endpoint for frontend consumers + - `src/mcpbridge_wrapper/__main__.py` broker-daemon Web UI wiring updated to publish live runtime status + - `tests/unit/test_broker_daemon.py`, `tests/unit/webui/test_server.py`, and `tests/unit/test_main.py` covering healthy and degraded runtime status flows +- **Acceptance Criteria:** + - [x] Dedicated broker host exposes structured runtime status including broker state, daemon pid, upstream pid (when present), version, and connected client count + - [x] Status makes reconnecting/not-ready states explicit so a frontend can distinguish them from a healthy shared daemon + - [x] Automated tests cover both healthy and degraded broker runtime status responses + +#### ⬜️ P6-T2: Build a terminal frontend for broker daemon monitoring and control +- **Description:** Implement a terminal-first operator interface for the broker daemon so users can explicitly see whether the daemon is running, whether upstream Xcode connectivity is healthy, which clients are attached, and what recent reconnect/error events occurred. The interface should give a clearer operational model than auto-spawn alone. +- **Priority:** P1 +- **Dependencies:** P6-T1 +- **Parallelizable:** no +- **Outputs/Artifacts:** + - `src/mcpbridge_wrapper/` TUI entrypoint/module for broker monitoring and control + - Tests covering the TUI status rendering and control integration where practical + - CLI/docs wiring for launching the TUI +- **Acceptance Criteria:** + - [ ] Users can launch a terminal UI from the wrapper package to inspect broker runtime state without tailing logs manually + - [ ] The TUI shows at minimum broker state, daemon/upstream PIDs, connected client count, and recent broker events or reconnect indicators + - [ ] The TUI exposes at least one explicit control action for the daemon lifecycle (for example stop or restart) + +#### ⬜️ P6-T3: Document the explicit dedicated-host frontend workflow +- **Description:** Update the operator docs so the recommended path for multi-editor setups is an explicit dedicated broker host plus a single monitoring frontend. The docs should explain when to prefer a dedicated host over implicit auto-spawn, how to verify that both editors share one daemon, and how the new frontend fits into that workflow. +- **Priority:** P2 +- **Dependencies:** P6-T1, P6-T2 +- **Parallelizable:** yes +- **Outputs/Artifacts:** + - `README.md` dedicated-host guidance updated to mention the explicit frontend + - `docs/broker-mode.md` and related docs updated with the recommended monitoring/control workflow + - Any new frontend usage documentation added under `docs/` +- **Acceptance Criteria:** + - [ ] README explains the dedicated-host + frontend workflow for users who want explicit visibility into daemon health + - [ ] Broker docs describe how to confirm that multiple editors are attached to one shared daemon + - [ ] Frontend launch and troubleshooting steps are documented in a user-facing guide diff --git a/src/mcpbridge_wrapper/__main__.py b/src/mcpbridge_wrapper/__main__.py index 9ae83e07..92558271 100644 --- a/src/mcpbridge_wrapper/__main__.py +++ b/src/mcpbridge_wrapper/__main__.py @@ -618,6 +618,12 @@ def main() -> int: metrics = None audit = None + def get_broker_status() -> Optional[Dict[str, Any]]: + """Return live broker runtime status for explicit frontend consumers.""" + if daemon is None: + return None + return daemon.status() + if web_ui_enabled: runtime = _prepare_webui_runtime( web_ui_port=web_ui_port, @@ -656,6 +662,7 @@ def request_broker_shutdown() -> None: audit, service_name="broker-daemon", request_stop=request_broker_shutdown, + broker_status_provider=get_broker_status, ) print( f"Web UI dashboard started at http://{config.host}:{config.port}", diff --git a/src/mcpbridge_wrapper/broker/daemon.py b/src/mcpbridge_wrapper/broker/daemon.py index e3308fc3..c02f823c 100644 --- a/src/mcpbridge_wrapper/broker/daemon.py +++ b/src/mcpbridge_wrapper/broker/daemon.py @@ -114,13 +114,31 @@ def upstream_initialized(self) -> asyncio.Event: def status(self) -> dict[str, Any]: """Return a dictionary describing the current daemon status.""" upstream_pid: int | None = None + upstream_alive = False if self._upstream is not None: with contextlib.suppress(Exception): upstream_pid = self._upstream.pid + with contextlib.suppress(Exception): + upstream_alive = self._upstream.returncode is None + + connected_clients = 0 + if self._transport is not None: + with contextlib.suppress(Exception): + connected_clients = len(self._transport.sessions) + return { "state": self._state.value, "pid": os.getpid(), "upstream_pid": upstream_pid, + "upstream_alive": upstream_alive, + "upstream_initialized": self._upstream_initialized.is_set(), + "tools_list_cached": self._tools_list_cache is not None, + "connected_clients": connected_clients, + "reconnect_attempt": self._reconnect_attempt, + "shutdown_requested": self._shutdown_requested, + "socket_path": str(self._config.socket_path), + "pid_file": str(self._config.pid_file), + "version_file": str(self._config.version_file), "version": __version__, } diff --git a/src/mcpbridge_wrapper/webui/server.py b/src/mcpbridge_wrapper/webui/server.py index d87d5d55..cbec0aa6 100644 --- a/src/mcpbridge_wrapper/webui/server.py +++ b/src/mcpbridge_wrapper/webui/server.py @@ -165,6 +165,7 @@ def create_app( audit: AuditLogger, service_name: str = "mcpbridge-wrapper", request_stop: Callable[[], None] | None = None, + broker_status_provider: Callable[[], dict[str, Any] | None] | None = None, ) -> FastAPI: """Create and configure the FastAPI application. @@ -174,6 +175,7 @@ def create_app( audit: Audit logger instance. service_name: Runtime service label exposed by control API. request_stop: Optional callback used by control API to request shutdown. + broker_status_provider: Optional callback returning broker runtime status. Returns: Configured FastAPI application. @@ -191,6 +193,7 @@ def create_app( app.state.audit = audit app.state.service_name = service_name app.state.request_stop = request_stop + app.state.broker_status_provider = broker_status_provider ws_clients: list[WebSocket] = [] app.state.ws_clients = ws_clients @@ -368,6 +371,32 @@ async def stop_service(request: Request) -> dict[str, str]: "message": f"Shutdown requested for {service_name}.", } + @app.get("/api/broker/status") + async def get_broker_status(request: Request) -> dict[str, Any]: + """Get structured broker runtime status when this runtime can provide it.""" + _check_auth(request, config) + if broker_status_provider is None: + return {"available": False, "service_name": service_name, "broker": None} + + try: + broker_status = broker_status_provider() + except Exception as exc: + return { + "available": False, + "service_name": service_name, + "broker": None, + "error": str(exc), + } + + if broker_status is None: + return {"available": False, "service_name": service_name, "broker": None} + + return { + "available": True, + "service_name": service_name, + "broker": broker_status, + } + # --- API: Health --- @app.get("/api/health") @@ -419,6 +448,7 @@ def run_server( on_started: Callable[[], None] | None = None, service_name: str = "mcpbridge-wrapper", request_stop: Callable[[], None] | None = None, + broker_status_provider: Callable[[], dict[str, Any] | None] | None = None, ) -> None: """Start the web UI server (blocking). @@ -429,6 +459,7 @@ def run_server( on_started: Optional callback invoked after server starts. service_name: Runtime service label exposed by control API. request_stop: Optional callback used by control API to request shutdown. + broker_status_provider: Optional callback returning broker runtime status. """ _require_webui_deps() assert uvicorn is not None @@ -438,6 +469,7 @@ def run_server( audit, service_name=service_name, request_stop=request_stop, + broker_status_provider=broker_status_provider, ) server_config = uvicorn.Config( @@ -486,6 +518,7 @@ def run_server_in_thread( audit: AuditLogger, service_name: str = "mcpbridge-wrapper", request_stop: Callable[[], None] | None = None, + broker_status_provider: Callable[[], dict[str, Any] | None] | None = None, ) -> threading.Thread: """Start the web UI server in a daemon thread. @@ -495,6 +528,7 @@ def run_server_in_thread( audit: Audit logger instance. service_name: Runtime service label exposed by control API. request_stop: Optional callback used by control API to request shutdown. + broker_status_provider: Optional callback returning broker runtime status. Returns: The daemon thread running the server. @@ -508,6 +542,7 @@ def run_server_in_thread( "audit": audit, "service_name": service_name, "request_stop": request_stop, + "broker_status_provider": broker_status_provider, }, daemon=True, name="webui-server", diff --git a/tests/unit/test_broker_daemon.py b/tests/unit/test_broker_daemon.py index 2747043a..4066f7ac 100644 --- a/tests/unit/test_broker_daemon.py +++ b/tests/unit/test_broker_daemon.py @@ -72,6 +72,12 @@ def test_status_before_start(self, tmp_path: Path) -> None: assert status["state"] == "init" assert status["pid"] == os.getpid() assert status["upstream_pid"] is None + assert status["upstream_alive"] is False + assert status["upstream_initialized"] is False + assert status["tools_list_cached"] is False + assert status["connected_clients"] == 0 + assert status["socket_path"] == str(cfg.socket_path) + assert status["pid_file"] == str(cfg.pid_file) # --------------------------------------------------------------------------- @@ -510,11 +516,21 @@ async def _block(*a, **kw) -> bytes: # type: ignore[no-untyped-def] new=AsyncMock(return_value=proc), ): await daemon.start() + daemon._upstream_initialized.set() + daemon._tools_list_cache = '{"jsonrpc":"2.0","result":{"tools":[]}}' + daemon._transport = MagicMock() + daemon._transport.sessions = {1: MagicMock(), 2: MagicMock()} s = daemon.status() assert s["state"] == "ready" assert s["upstream_pid"] == proc.pid assert s["pid"] == os.getpid() + assert s["upstream_alive"] is True + assert s["upstream_initialized"] is True + assert s["tools_list_cached"] is True + assert s["connected_clients"] == 2 + assert s["socket_path"] == str(cfg.socket_path) + assert s["pid_file"] == str(cfg.pid_file) daemon._stop_event.set() if daemon._read_task and not daemon._read_task.done(): diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index b3ca5168..8132a544 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1376,6 +1376,7 @@ def test_main_broker_daemon_webui_wires_metrics_and_audit_into_transport(self): metrics, audit, service_name="broker-daemon", + broker_status_provider=ANY, request_stop=ANY, ) request_stop = run_server_in_thread.call_args.kwargs["request_stop"] diff --git a/tests/unit/webui/test_server.py b/tests/unit/webui/test_server.py index ca1ba89b..040d4212 100644 --- a/tests/unit/webui/test_server.py +++ b/tests/unit/webui/test_server.py @@ -265,6 +265,47 @@ def test_control_capability_default_disables_stop(self, client): assert data["service_name"] == "mcpbridge-wrapper" assert data["can_stop"] is False + def test_broker_status_default_reports_unavailable(self, client): + """Broker status API reports unavailable when no runtime provider is wired.""" + response = client.get("/api/broker/status") + assert response.status_code == 200 + assert response.json() == { + "available": False, + "service_name": "mcpbridge-wrapper", + "broker": None, + } + + def test_broker_status_returns_runtime_payload(self, config, metrics, audit): + """Broker status API returns the provider payload when wired.""" + broker_status = { + "state": "ready", + "pid": 1234, + "upstream_pid": 5678, + "upstream_alive": True, + "upstream_initialized": True, + "tools_list_cached": True, + "connected_clients": 2, + "socket_path": "/tmp/broker.sock", + "pid_file": "/tmp/broker.pid", + "version": "0.4.1", + } + app = create_app( + config, + metrics, + audit, + service_name="broker-daemon", + broker_status_provider=lambda: broker_status, + ) + client = TestClient(app) + + response = client.get("/api/broker/status") + assert response.status_code == 200 + assert response.json() == { + "available": True, + "service_name": "broker-daemon", + "broker": broker_status, + } + def test_control_stop_unsupported_returns_conflict(self, client): """Stop requests return 409 when shutdown callback is not configured.""" response = client.post("/api/control/stop")