| Phase | Surface | Status | Tests executed | Notes |
|---|---|---|---|---|
| Phase 1 | CLI (doctor) |
PASS | 6 | All 29 codes registered; doctor fix dry-run + execute exercised; classifier maps stdio-spawn-enoent. |
| Phase 2 | Web UI (ErrorPanel) | PASS | 1 integration flow | ErrorPanel renders full payload; Preview (dry-run) button fires fix endpoint → 200. |
| Phase 3 | macOS Tray | SKIPPED | 0 | Production tray actively running; stopping mid-session was judged too risky. Visual confirm recommended post-merge. |
Ran the freshly-built dev mcpproxy on an isolated port (127.0.0.1:18080) and data dir
(/tmp/mcpproxy-test-spec044) with a purpose-built test config containing a broken-stdio
server whose command points at /nonexistent/binary. The production daemon on :8080 was
never touched; its config was backed up (byte-identical after test) and the dev binary was run from the worktree.
doctor list-codes — all 29 diagnostic codes registeredlist-codes enumerates them with docs + fix rows.
Actual:jq length == 29. All 5 MCPX_STDIO codes present. Each entry surfaces severity, docs link, and fix rows (command / button / link).
$ ./mcpproxy doctor list-codes 29 diagnostic codes registered: MCPX_CONFIG_DEPRECATED_FIELD warn The configuration uses a deprecated field that will be removed in a future release. docs: docs/errors/MCPX_CONFIG_DEPRECATED_FIELD.md fix (button): Preview migration (dry-run) key=config_migrate_deprecated [destructive -> dry-run default] fix (link): Migration notes docs/errors/MCPX_CONFIG_DEPRECATED_FIELD.md MCPX_CONFIG_MISSING_SECRET error The configuration references a secret that is not defined. docs: docs/errors/MCPX_CONFIG_MISSING_SECRET.md fix (command): List secrets mcpproxy secret list fix (link): Secret references docs/errors/MCPX_CONFIG_MISSING_SECRET.md ... (27 more) ... $ ./mcpproxy doctor list-codes -o json | jq 'length' 29 $ ./mcpproxy doctor list-codes -o json | jq '[.[] | select(.code | startswith("MCPX_STDIO_"))] | map(.code)' [ "MCPX_STDIO_EXIT_NONZERO", "MCPX_STDIO_HANDSHAKE_INVALID", "MCPX_STDIO_HANDSHAKE_TIMEOUT", "MCPX_STDIO_SPAWN_EACCES", "MCPX_STDIO_SPAWN_ENOENT" ]
doctor --server broken-stdio — per-server health checkbroken-stdio, maps to MCPX_STDIO_SPAWN_ENOENT.
Actual:With socket enabled, produces ⚠ Found 1 issue that need attention and ❌ Upstream Server Connection Errors for broken-stdio. Classifier correctly picks up the zsh:1: no such file or directory stderr pattern.
enable_socket: false failed with "doctor requires running daemon. Start with: mcpproxy serve"
— misleading because the daemon was running and reachable over HTTP. Re-running with socket enabled
resolved the issue. Worth a CLI UX follow-up.
$ ./mcpproxy doctor --server broken-stdio ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔍 MCPProxy Health Check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Version: v0.24.9 (latest) ⚠ Found 1 issue that need attention ❌ Upstream Server Connection Errors Server: broken-stdio ⚠️ Deprecated Configuration • features features is deprecated and has no effect Suggestion: Remove from config (all feature flags are unused) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
$ ./mcpproxy doctor --server broken-stdio # enable_socket: false Error: doctor requires running daemon. Start with: mcpproxy serve # ...usage text elided... Error: doctor requires running daemon. Start with: mcpproxy serve
doctor fix MCPX_STDIO_SPAWN_ENOENT --server broken-stdio — dry-run defaultstdio_show_last_logs
success, Mode: dry_run, preview text returned.
$ ./mcpproxy doctor fix MCPX_STDIO_SPAWN_ENOENT --server broken-stdio ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🛠 Doctor Fix: MCPX_STDIO_SPAWN_ENOENT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Server: broken-stdio Fix step: Show last server log lines Fixer key: stdio_show_last_logs Destructive: no Mode: dry_run Outcome: ✅ success Preview: Server 'broken-stdio' log tail unavailable in this build — enable server-side log access to view the last 50 lines here. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
doctor fix ... --execute — rate-limited safety guardPOST /api/v1/diagnostics/fix with {"mode":"execute",...} returned 200 {"mode":"execute","outcome":"success"}.
$ ./mcpproxy doctor fix MCPX_STDIO_SPAWN_ENOENT --server broken-stdio --execute Error: fix invocation failed: API returned status 429: { "success":false, "error":"Too many fix attempts; try again shortly", "request_id":"eeb5791b-06ab-4055-aa2f-1acabf83bf42" }
/diagnostics, /servers, /servers/{name}/diagnosticsGET /api/v1/servers/broken-stdio/diagnostics returns full Diagnostic payload: error_code, user_message, fix_steps[] (command / button / link variants), docs_url, severity, detected_at, health.level=unhealthy, health.action=restart.GET /api/v1/servers returns each server with top-level error_code ("MCPX_STDIO_SPAWN_ENOENT" on the broken one, null on healthy).GET /api/v1/diagnostics aggregates to total_issues: 1, upstream_errors[0].server_name == "broken-stdio"."mode": "dry_run" | "execute" (see Findings #3), not a boolean.Despite the spawn being wrapped via /bin/zsh -l -c, the classifier parses the zsh stderr line
zsh:1: no such file or directory: /nonexistent/binary and maps it to MCPX_STDIO_SPAWN_ENOENT
(not a generic timeout or handshake error). This is the core value-add of the spec.
Opened http://127.0.0.1:18080/ui/servers/broken-stdio?apikey=*** in claude-in-chrome. The Vue
ErrorPanel component (4d92c82) rendered the full diagnostic payload with all expected
elements. Clicking the Preview (dry-run) button fires the fix endpoint successfully.
/ui/servers/broken-stdioerrorMCPX_STDIO_SPAWN_ENOENTzsh:1: no such file or directory: /nonexistent/binarywhich npx && which uvx && which python3 + Copy buttondocs/errors/MCPX_STDIO_SPAWN_ENOENT.mdConnecting (yellow)POST /api/v1/diagnostics/fixPOST http://127.0.0.1:18080/api/v1/diagnostics/fix with {"mode":"dry_run","code":"MCPX_STDIO_SPAWN_ENOENT","server":"broken-stdio","fixer_key":"stdio_show_last_logs"}
Response:200 OK + JSON body containing preview string
UX observation:No toast or inline render of data.preview after success. Re-verified via direct curl that the payload does contain the preview text.
ss_103222r6g and
ss_0723w5ok4). Those IDs are ephemeral and were not written to disk — no
/tmp/spec044-verify-webui-*.png files exist. The textual test plan above documents every element that
was visually confirmed.
/ui/servers/broken-stdioMCPProxy tray app and mcpproxy
core were actively running on port 8080 at the time of this verification. Killing and restarting
them to swap in the dev tray binary (6aa8305) would have risked disrupting an active session for no
critical upside — Phases 1 + 2 already demonstrate end-to-end that the classifier, REST API, CLI formatter, fix
endpoint, and Vue ErrorPanel all work correctly for MCPX_STDIO_SPAWN_ENOENT.
total_issues > 0/ui/servers/<name> in the default browser
Relevant code lives in the commit 6aa8305 — feat(spec-044): macOS tray badge + Fix issues menu group.
To verify post-merge, run:
$ # after-hours, when it's safe to cycle production $ cd ~/repos/mcpproxy-go $ make build $ pkill -x MCPProxy $ open /tmp/MCPProxy.app
doctor CLI has no HTTP fallback when socket is disabled
medium
With "enable_socket": false in config, mcpproxy doctor fails with
"doctor requires running daemon. Start with: mcpproxy serve" — a misleading message, because the daemon
is already running and reachable over HTTP at the configured listen address. Env vars
MCPPROXY_LISTEN/MCPPROXY_API_KEY have no effect on the doctor subcommand either.
Reproduction: start mcpproxy serve with enable_socket: false, then run
mcpproxy doctor.
Suggested fix: either (a) add HTTP fallback using listen + API key, or (b)
surface a clearer error: "socket is disabled in config — re-enable it, or use
curl http://127.0.0.1:PORT/api/v1/diagnostics".
Clicking the Preview (dry-run) button in ErrorPanel.vue fires the fix endpoint and receives
a 200 response whose JSON body contains a helpful preview string (e.g.,
"Server 'broken-stdio' log tail unavailable in this build..."), but the Vue component does not render
it anywhere. Users have no visual feedback that the action succeeded.
Location: web/frontend/src/components/ErrorPanel.vue (or equivalent — commit
4d92c82).
Suggested fix: surface response.data.preview via a DaisyUI toast, or render it
inline below the button while preview is populated.
"mode" string, not a boolean "dry_run"
low
The POST /api/v1/diagnostics/fix endpoint expects {"mode": "dry_run" | "execute", ...},
not a boolean flag like {"dry_run": true}. This matches the implementation at
internal/httpapi/diagnostics_fix.go:27, but worth calling out explicitly in the OpenAPI description
and in any published examples to prevent API-consumer confusion.
Suggested fix: add an inline note in oas/swagger.yaml and the spec's
contracts/ examples explicitly showing the string-enum field.
list-codes -o json | jq length/nonexistent/binary correctly classifies to MCPX_STDIO_SPAWN_ENOENT even when wrapped through /bin/zsh -l -ccode, severity, user_message, cause, fix_steps[], docs_url, detected_atserver.health block populated with level: unhealthy, action: restart — matches Spec 044's action-suggestion designerror_code field at top leveldoctor fix rate-limiter returns structured 429 with a request_id for correlation| Go toolchain | go1.25.1 darwin/arm64 |
| Binary version | v0.24.9 (reported by the daemon banner; mcpproxy version subcommand is absent from this build — not yet ported to Cobra) |
| Binary size | 40,874,114 bytes (/Users/user/repos/mcpproxy-go-diagnostics-taxonomy/mcpproxy, 2026-04-24 18:45) |
| Worktree | /Users/user/repos/mcpproxy-go-diagnostics-taxonomy |
| Branch | feat/diagnostics-taxonomy @ 911704cc539e8c4965e7c1786cbcf3b0b70e0ae6 |
| Commits-under-test | 9 (see header) |
| Test data dir | /tmp/mcpproxy-test-spec044 (removed during cleanup) |
| Test listen | 127.0.0.1:18080 |
| Production untouched | :8080, config byte-identical to backup (diff -q) |
| Duration | ~45 minutes (setup + 6 CLI tests + 2 Web UI checks + cleanup) |
# 1. back up production config, but don't touch it $ cp ~/.mcpproxy/mcp_config.json /tmp/mcp_config.json.backup-$(date +%s) # 2. build dev binary on the feature branch $ cd ~/repos/mcpproxy-go-diagnostics-taxonomy $ make build # 3. isolated data dir + config $ mkdir -p /tmp/mcpproxy-test-spec044 $ cat > /tmp/mcpproxy-test-spec044/config.json <<'JSON' { "listen": "127.0.0.1:18080", "api_key": "***", "enable_socket": true, "enable_web_ui": true, "mcpServers": [ {"name":"broken-stdio", "command":"/nonexistent/binary", "protocol":"stdio", "enabled":true}, {"name":"healthy-control", "command":"echo", "protocol":"stdio", "enabled":false} ] } JSON # 4. launch in tmux on the isolated port + data dir $ tmux new-session -d -s spec044 \ "./mcpproxy serve -c /tmp/mcpproxy-test-spec044/config.json \ -d /tmp/mcpproxy-test-spec044 --log-level=debug" # 5. exercise CLI + REST + Web UI $ ./mcpproxy doctor list-codes -o json | jq length $ ./mcpproxy doctor --server broken-stdio $ ./mcpproxy doctor fix MCPX_STDIO_SPAWN_ENOENT --server broken-stdio $ curl -s -H "X-API-Key: ***" \ http://127.0.0.1:18080/api/v1/servers/broken-stdio/diagnostics | jq . # 6. cleanup $ tmux kill-session -t spec044 $ rm -rf /tmp/mcpproxy-test-spec044 $ diff -q ~/.mcpproxy/mcp_config.json /tmp/mcp_config.json.backup-*
/Users/user/repos/mcpproxy-go/tmp-agent-report-spec044-verify.md (structured verification report, 9,038 bytes)/tmp/spec044-verify-cli.log (CLI command outputs, 10,091 bytes)/tmp/mcpproxy-test-spec044/server.log (server log — not found, test data dir was cleaned up)/tmp/spec044-verify-webui-*.png (Web UI screenshots — not found, captured via claude-in-chrome but not persisted to disk)/tmp/spec044-verify-tray-*.png (tray screenshots — not found, Phase 3 skipped)