Skip to content

feat(cli): mcp-debugger doctor — environment self-check command (#423, part 1) - #434

Merged
debugmcpdev merged 6 commits into
mainfrom
feature/423-doctor
Aug 23, 2026
Merged

feat(cli): mcp-debugger doctor — environment self-check command (#423, part 1)#434
debugmcpdev merged 6 commits into
mainfrom
feature/423-doctor

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Implements the CLI half of #423: a mcp-debugger doctor subcommand that checks every adapter's runtime prerequisites in one pass. (The consolidated docs/diagnostics.md follows in a second PR, which will close the issue.)

What it does

mcp-debugger doctor [languages...] [--json] [--timeout <ms>]

Prints one table — adapter → runtime found (path + version) → debug backend found → verdict + fix hint — for all nine adapters, plus host-platform checks (Linux Yama ptrace_scope for attach, container-mode detection + /workspace mount sanity). Sample from this machine:

mcp-debugger doctor 0.24.2 (win32-x64, node v24.14.1)

Adapter     Runtime                                             Debug backend                        Verdict
mock        (built-in)                                          (built-in)                           ✅ ok
python      Python 3.13.12 C:\...\py.EXE                        debugpy 1.8.20                       ✅ ok
javascript  Node.js v24.14.1                                    js-debug (vendored)                  ✅ ok
ruby        Ruby 3.4.9 C:\Ruby34-x64\bin\ruby.exe               rdbg 1.11.0 C:\Ruby34-x64\bin\rdbg.bat  ✅ ok
rust        Rust 1.94.1                                         CodeLLDB 1.11.8 (vendored) C:\...    ⚠️ warn
go          Go 1.26.1 C:\Program Files\Go\bin\go.exe            Delve 1.26.3 C:\...\dlv.exe          ✅ ok
java        Java 21.0.10 C:\...\jdk-21.0.10.7-hotspot\bin\java.exe  JDI bridge C:\...\java\out       ✅ ok
dotnet      .NET SDK 8.0.420                                    netcoredbg 3.1.3-1 C:\...\netcoredbg.exe  ✅ ok
cpp         C/C++ compiler g++ (Rev14, Built by MSYS2) 15.2.0   CodeLLDB 1.11.8 (vendored) C:\...    ✅ ok

Platform checks
  ✅ container mode: not running in container mode
  — workspace mount: host mode
  — yama ptrace_scope: linux only

Fixes
  rust: Rust MSVC toolchain detected. CodeLLDB works best with the GNU toolchain (x86_64-pc-windows-gnu) or DWARF debug info.

1 of 9 adapters need attention. Run 'mcp-debugger doctor <language>' to gate the exit code on a specific language.

--json emits a stable schemaVersion: 1 report for machine consumption (verdict enum + reason strings + raw validate details + per-probe timing).

Exit codes: with positional languages, 0 = every requested language ok/warn, 1 = any requested language broken/missing/disabled or an unknown name; with no positionals the run is informational and exits 0. 2 = doctor itself failed. CI can gate with mcp-debugger doctor python go.

Design

  • Reuses the probing the server already has (the issue's requirement): per language, one IAdapterFactory.validate() plus computeModeAvailability() — the exact rails list_supported_languages runs on — so doctor's launch/attach availability can never disagree with the server. Where the server deliberately fails open on probe errors (issue [FEATURE] start_debugging should fail fast when the target adapter is known-unavailable, instead of reporting success #360), doctor reports broken and records probe.failed/probe.timedOut so the divergence is visible instead of silent.
  • New probes land in the packages that own them, not in doctor:
    • codelldb-common: resolveCodeLLDBExecutableWithSource() — attributes which stage of the documented order won (vendored / env:CODELLDB_PATH / platform-package); the existing resolver delegates to it, and the rust/cpp factories now report codelldbSource in validate details.
    • adapter-python: getDebugpyVersion() — replaces the factory-private boolean probe (same spawn, now reports the version).
    • Doctor-only helpers, not called from validate() so registration and list_supported_languages cost is unchanged: getNetcoredbgVersion() + getDotnetSdkVersion() (dotnet), findJavacExecutable() (java — javac -g is required for variable inspection but the adapter itself never checked for it), getCompilerInfo() (cpp version banner).
  • Timeouts + hang containment: every probe runs in parallel under a per-language timeout (default 10 s). The existing probes have no timeouts and a hung toolchain child (rust spawns with shell: true) would otherwise hold the event loop open forever — after a timed-out probe, doctor drains stdout and force-exits. doctor --timeout 1 returns a full report in ~0.5 s.
  • CLI plumbing follows the check-rust-binary template: setupDoctorCommand in src/cli/setup.ts, handler in src/cli/commands/doctor/, output via process.stdout.write (console.* is noop'd process-wide), lazy import in the action (issue perf(startup): stdio mode eagerly imports express and all three transport stacks #400), no startup janitor (issue perf(startup): scope orphan reapers to server commands, share one /proc walk, allow opt-out #399 contract), exit code via process.exitCode. The npx bundle picks the command up automatically through cli-entry → main().

Testing

  • 60 new unit tests across tests/unit/cli/doctor/ (orchestration verdict matrix incl. fail-open parity, timeout via fake timers, platform checks, formatting, handler exit codes) + registration/wiring tests; all injected fakes, no spawns.
  • Package tests for each new helper (mocked spawn/fs), including byte-identical delegation of the refactored CodeLLDB resolver.
  • New e2e smoke (tests/e2e/doctor-smoke.test.ts): runs the built CLI, asserts only mock's verdict + report shape, so it is deterministic on every runner.
  • Verified live on Windows: all 9 adapters detected, exit-code matrix, JSON pipe, DEBUG_MCP_DISABLE_LANGUAGES gating, --help, timeout containment.

Part 1 of 2 for #423.

🤖 Generated with Claude Code

cynarlab and others added 3 commits August 22, 2026 18:49
- codelldb-common: resolveCodeLLDBExecutableWithSource() attributes which
  resolution stage won (vendored / env:CODELLDB_PATH / platform-package);
  resolveCodeLLDBExecutable() now delegates to it. Rust + cpp factories
  report codelldbSource in validate() details.
- adapter-python: getDebugpyVersion() replaces the factory-private boolean
  probe (same spawn); validate() details gain debugpyVersion.
- Doctor-only helpers (not called from validate(), so registration and
  list_supported_languages cost is unchanged): getNetcoredbgVersion() +
  getDotnetSdkVersion() (dotnet), findJavacExecutable() (java),
  getCompilerInfo() (cpp).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One command that answers "why is this language unavailable and how do I
fix it": doctor prints adapter -> runtime (path + version) -> debug
backend -> verdict + fix hint for all nine adapters, plus host-platform
checks (Linux Yama ptrace_scope, container workspace mount).

- doctor [languages...] [--json] [--timeout <ms>]; exit 0/1 gates on the
  requested languages (overview mode always exits 0), 2 = doctor error.
- Reuses the exact probing rails of list_supported_languages (one
  factory.validate() per language + computeModeAvailability) so doctor
  verdicts can never disagree with the server; where the server fails
  open on probe errors, doctor reports broken and records probe.failed /
  probe.timedOut so the divergence is visible.
- Per-language timeout (default 10s), all probes in parallel; a
  timed-out probe forces exit after a stdout drain so a hung toolchain
  child cannot wedge the process.
- console.* is noop'd process-wide, so output goes through
  process.stdout.write (check-rust-binary pattern); no janitor for
  doctor (issue #399 contract); handler lazy-imported (issue #400).
- npx bundle picks the command up automatically via cli-entry -> main().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cynarlab and others added 3 commits August 22, 2026 19:21
Found running doctor in the Linux container: ruby reported broken there,
but container ruby is attach-only by design — direct-connect attach runs
the debug engine inside the debuggee and needs no local toolchain, and
list_supported_languages reports attach as available. Gating a container
job on 'doctor ruby' would have failed falsely.

When the launch toolchain probe fails but attach remains available, the
verdict is now warn (launch errors still listed, plus an explicit note),
and a gated run passes. Verdict stays broken when neither mode works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t verdicts (#423)

Code-review batch (14 findings addressed; #435 filed for the two
architectural ones):

Probe correctness:
- All five new spawn probes (getDebugpyVersion, getNetcoredbgVersion,
  getDotnetSdkVersion, cpp captureVersionLine, java validate probe) now
  read output on 'close' instead of 'exit' — the stdio-drain race this
  repo already fixed once in ruby-utils (a399316) — and kill their
  child after a 10s guard timeout so probes never strand processes.
  Test spawn fakes across six files now emit 'close' like real children.
- java: validateJavacExecutable was a line-for-line copy of
  validateJavaExecutable — deleted; findJavacExecutable reuses the
  shared probe. python: utils-level hasDebugpy delegates to
  getDebugpyVersion (adapter's cached copy left; its tests replace the
  utils module wholesale). cpp: getCompilerInfo(command?) skips the
  candidate re-scan when validate() already found the compiler.

Doctor behavior:
- npx bundle: collectDoctorExtras' variable-specifier dynamic import can
  never resolve in the published CLI (adapters are bundled, not
  dependencies) — switched to literal-specifier imports esbuild can
  inline. Verified by installing the packed tarball into a bare dir:
  all four extras now resolve with no adapter packages on disk.
- A hung extras child now sets probe.timedOut so the handler's
  force-exit containment covers it; extras run inside the language's
  remaining timeout budget and probe.durationMs spans both phases.
- --timeout is parsed as strict digits ('1e4'/'10s' are usage errors,
  not 1ms/10ms probe budgets).
- An installed adapter whose factory fails to load is broken (exit 1
  when requested), not warn — with the load error surfaced.
- factory.getMetadata() is fault-isolated so one malformed factory
  cannot collapse the whole report.
- Platform check warns when MCP_CONTAINER is set to a truthy-looking
  but unrecognized value (=1/TRUE) instead of blessing it as host mode.
- presentLanguage only renders a runtime/backend cell when something
  was detected — an absent toolchain now shows '—' instead of its label.
- CLAUDE.md new-adapter checklist gains the doctor wiring + smoke-count
  steps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@debugmcpdev
debugmcpdev merged commit 7cca1d7 into main Aug 23, 2026
8 of 9 checks passed
@debugmcpdev
debugmcpdev deleted the feature/423-doctor branch August 23, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants