feat(cli): mcp-debugger doctor — environment self-check command (#423, part 1) - #434
Merged
Conversation
- 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>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the CLI half of #423: a
mcp-debugger doctorsubcommand that checks every adapter's runtime prerequisites in one pass. (The consolidateddocs/diagnostics.mdfollows in a second PR, which will close the issue.)What it does
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_scopefor attach, container-mode detection +/workspacemount sanity). Sample from this machine:--jsonemits a stableschemaVersion: 1report 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 exits0.2= doctor itself failed. CI can gate withmcp-debugger doctor python go.Design
IAdapterFactory.validate()pluscomputeModeAvailability()— the exact railslist_supported_languagesruns 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 reportsbrokenand recordsprobe.failed/probe.timedOutso the divergence is visible instead of silent.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 reportcodelldbSourcein validate details.adapter-python:getDebugpyVersion()— replaces the factory-private boolean probe (same spawn, now reports the version).validate()so registration andlist_supported_languagescost is unchanged:getNetcoredbgVersion()+getDotnetSdkVersion()(dotnet),findJavacExecutable()(java —javac -gis required for variable inspection but the adapter itself never checked for it),getCompilerInfo()(cpp version banner).rustspawns withshell: true) would otherwise hold the event loop open forever — after a timed-out probe, doctor drains stdout and force-exits.doctor --timeout 1returns a full report in ~0.5 s.check-rust-binarytemplate:setupDoctorCommandinsrc/cli/setup.ts, handler insrc/cli/commands/doctor/, output viaprocess.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 viaprocess.exitCode. The npx bundle picks the command up automatically throughcli-entry → main().Testing
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.tests/e2e/doctor-smoke.test.ts): runs the built CLI, asserts only mock's verdict + report shape, so it is deterministic on every runner.DEBUG_MCP_DISABLE_LANGUAGESgating,--help, timeout containment.Part 1 of 2 for #423.
🤖 Generated with Claude Code