Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **`mcp-debugger doctor` — environment self-check command** — one command renders a per-language table of runtime/backend toolchain state (found/missing, versions, paths) with actionable install hints, so "why doesn't language X work" is a 5-second diagnosis instead of a log spelunk. Adapters own their presentation via a new `describeToolchain()` factory hook; a consolidated diagnostics guide cross-links doctor, logs, `dryRunSpawn`, and `DAP_TRACE` (#423)
- **Adapter availability unified on one shared probe** — the server's language gate, `list_supported_languages`, and doctor all consult the same per-entry toolchain probe (TTL-cached), and the adapter registry surface is fully typed (`IAdapterRegistry`); `tools/list` no longer imports all 9 adapters to answer availability (#435)
- **Scheduled canary: install-and-debug matrix over the published artifacts** — a workflow (also the post-release gate) installs the *published* npm package (default and `--omit=optional` + `CODELLDB_PATH` legs), runs npx, pulls the Docker image, and drives a real breakpoint → variables → continue cycle in mock/python/rust across x64/arm64 Linux, arm64 macOS, and Windows — packaging regressions surface on a schedule instead of in user reports (#425)
- **Weekly kind smoke job for the Kubernetes recipe** — spins up kind, applies the registry-free example manifests, and drives attach + a function breakpoint through the Streamable HTTP sidecar, so the #424 recipe stays verified against current images (#451)
- **The debugging skill and in-band server instructions point at the Kubernetes attach recipe** — remote-attach guidance now lands in the context agents actually read (#452)
- **`get_stack_trace` accepts `threadId`** — inspect a specific thread (ids from `list_threads`); when it reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls. The escape hatch for sessions anchored to a frameless or arbitrary thread (#465)
- **`redefine_classes` reports `anchorResolution`** — statement-anchored breakpoints are re-resolved against the new source after a hot-swap and re-sent to the JVM, with the same `{moved, stale}` shape `restart_debugging` returns (see Fixed, #464)
- **Repeatable memory-footprint benchmark** — `scripts/mem-bench.mjs` + an advisory workflow track RSS across idle/busy scenarios (#395)
- **ROADMAP.md** — the path to 1.0, published (#429)
- **Turnkey Kubernetes debugging recipe** — new [docs/kubernetes.md](docs/kubernetes.md) operational reference (pattern decision table, worked port-forward and ephemeral-sidecar examples, attach path rules, security notes, EKS notes) plus registry-free example manifests under [examples/kubernetes/](examples/kubernetes/) — one self-contained YAML per attach-capable language built from stock public images with the app source in a ConfigMap (initContainer compiles for java/cpp), so `kubectl apply` works identically on kind and managed clusters with no image build or registry — and per-language copy-paste [attach presets](examples/kubernetes/attach-presets.md). Verified end-to-end on kind and Amazon EKS with the published `debugmcp/mcp-debugger:latest` sidecar. Also corrects the JIT-diagnostics tutorial's Part 1 attach call: `localRoot`/`remoteRoot` were silently dropped by the python adapter (no path-mapping lever exists) — breakpoints on attach use debuggee-side paths (#424)
- **CodeLLDB ships as per-platform npm packages (esbuild pattern)** — five new packages `@debugmcp/codelldb-{win32-x64,darwin-x64,darwin-arm64,linux-x64,linux-arm64}` (versioned by the CodeLLDB release, currently 1.11.8, payload staged from the digest-pinned VSIXs) are `optionalDependencies` of `@debugmcp/mcp-debugger`, so npm installs exactly the one matching your os/cpu. Rust and C/C++ debugging now work out of the box on every platform npm serves — previously the CLI tarball bundled linux-x64 only — and the core tarball shrinks from ~54 MB to a few MB. The resolver probes the installed platform package last — after the vendor tree and after `CODELLDB_PATH` — so an explicit `CODELLDB_PATH` still overrides the auto-installed package, and installs with `--omit=optional` keep working via `CODELLDB_PATH` (#383)

### Changed
- **Attach verification window default raised 5s → 20s** — the `attach_to_process` thread-poll deadline only ever bites when the debug adapter is alive but the target is slow to become debuggable (js-debug child-session adoption on a loaded host, a warming JVM); adapter death still fails fast via the proxy-gone latch, and the poll returns the moment threads appear, so healthy attaches never pay for the headroom. A false "attach failed" on a healthy target is strictly worse for an agent than a slower genuine failure. Pass a small `verifyTimeout` for fast failure-by-design probes
- **Ruby rejects logpoints up front** — rdbg does not advertise `supportsLogPoints` and silently converted a logpoint into a *pausing* breakpoint (the inverse of the contract) while every response read as success; `set_breakpoint` with `logMessage` on ruby now fails fast with the same clear error java/dotnet produce. For adapters whose support is genuinely unknown (dynamically loaded), the deferred verdict now actually arrives: the `start_debugging` response warns when live capabilities lack logpoint support and names each downgraded logpoint (#469)
- **Startup performance batch** — SSE/HTTP transport stacks lazy-import in CLI actions, availability probes are metadata-only, orphan reapers are scoped to server commands and share one bounded process scan (with an opt-out), and the DAP trace is opt-in (`DAP_TRACE=1`) with the per-session proxy log honoring the effective log level (#407–#411)
- **`packageManager` pinned to pnpm@10.33.0** — a different pnpm major regenerated `pnpm-lock.yaml` incompatibly (one contributor diff silently dropped the entire security `overrides` block); the pin plus a CONTRIBUTING note make the wrong-pnpm case fail fast, and the workflows read the pin instead of duplicating a version (#478)

### Fixed
- **Java attach is inspectable on the first try** — the JDI bridge never suspends the VM on attach and java's policy lacked the post-attach pause every other attach adapter has, so the session reported `paused` while the JVM kept running: empty stack, "the debugger may not be paused", and no discoverable recovery. Java now pauses the whole VM after attach (the bridge anchors its stopped event to a thread that can actually report frames), the frameless-thread notes name a real recovery (`threadId` targeting or continue+pause), and `get_local_variables` no longer contradicts the session state (#465)
- **`get_local_variables` walks down past an empty runtime top frame** — a pause inside a blocking syscall/sleep put a stdlib frame with no locals at `stackFrames[0]` and the tool returned an empty array even though the user frame's locals were one frame down and *already fetched*; extraction now re-anchors to the first frame that yields locals (zero extra DAP round-trips), reports that frame in `frame`, and a `note` discloses the walk-down. Skipped under an explicit `names:` filter (#468)
- **Statement-anchored breakpoints survive `redefine_classes`** — the hot-swap replant was purely by line, so a line-shifting swap silently rebound a `statement:`-anchored breakpoint to whatever now lives at the old line, reporting `verified: true`; anchors are now re-resolved against the new source (the `restart_debugging` machinery) and re-sent after the redefine so the JDI replant binds the moved lines. The bridge's `replantedBreakpoints` count also no longer drops re-planted function breakpoints (#464)
- **Unbound breakpoints surface where the agent is looking** — a launch that ran to completion with breakpoints that never bound now says so in the `start_debugging` response `warning` (with each breakpoint's stored diagnostic, e.g. CodeLLDB's valid-location path suggestion); and a function-breakpoint name the server *knows* can never bind is corrected instead of stored dead: go's bare `main` auto-qualifies to `main.main` with the rewrite disclosed (`requestedName` + warning) via a new `normalizeFunctionBreakpointName` policy hook (#467)
- **Verified JS breakpoints no longer carry the provisional "unbound" note** — js-debug's pending-target stub stamps `breakpoint.provisionalBreakpoint` (a raw l10n key, untranslated because the adapter spawn has no l10n bundle) and nothing cleared it on bind, so `list_breakpoints` asserted `verified: true` alongside a message meaning "Unbound breakpoint". Messages are now normalized at every stamp site: known keys translate to their English fallbacks, and a provisional note — in raw or translated form — cannot survive verification, including when the bind event carries no message at all. Thanks @MsfPablo for the fix (#471)
- **rdbg launches no longer die on a transient framing hiccup** — the DAP frame decoder discarded everything after a malformed header block, so junk bytes ahead of rdbg's `initialize` response took the response down with them and the launch failed hard at the 30s init deadline blaming adapter startup; the decoder now resyncs at the next header inside the remainder (junk costs one warning, not the session), and a header-decode error while requests are pending is logged at error level naming the request seq(s) (#470)
- **`dist-tarball/mcp-debugger-latest.tgz` is npm-installable again** — `build-npx-tarball.cjs` packed without the prepare-pack rewrite, so the tarball shipped pnpm-only `workspace:*` specifiers (`EUNSUPPORTEDPROTOCOL` on install) and the npx test arm silently resolved CodeLLDB from the repo's own vendor tree; the pack is now wrapped in prepare/restore (restore survives a failed pack) and the build fails if the packed manifest still contains `workspace:`. Thanks @abhijeetnardele24-hash for the fix (#463)
- **Docker Rust type summaries render again** — the image has no rustc, so CodeLLDB could not locate the toolchain's LLDB formatter scripts and Vec/String/HashMap printed as opaque pointers; the image now vendors the formatter scripts and sets `CODELLDB_RUST_SYSROOT` (injected as `_adapterSettings.scriptConfig.lang.rust.sysroot`), with an honest degradation warning on hosts where the sysroot genuinely cannot be found (#441)
- **Python attach honors `adapterConfig.pathMappings` — and dropped attach keys now warn instead of vanishing** — the python attach transform was a closed allowlist, so every adapterConfig extra (including debugpy's native `pathMappings: [{localRoot, remoteRoot}]`) was silently discarded and breakpoints at local-checkout paths could never bind against a remote debugpy (the #424 Kubernetes work documented this as a hard limitation). The transform is now a deny-list (the cpp pattern): `pathMappings` and any other debugpy attach knob pass through to the attach request, while the keys debugpy rejects alongside `connect.*` (top-level `host`/`port`), launch-template pollution (`console`), and the ptvsd-era `localRoot`/`remoteRoot` sugar stay stripped. The generic footgun is closed too: caller-provided `adapterConfig` keys that any adapter's attach transform fails to carry into the DAP attach request are named in a top-level `warning` on the `attach_to_process` response — unknown attach keys now either work or warn (#450)
- **Logpoints (and any never-paused launch) no longer read as unbound after firing** — `list_breakpoints` used to report `verified: false` with no `adapterId` forever for a launch that ran to completion without pausing, because the store's only reconciliation was a post-launch re-sync gated on the session still being live — exactly the gate a logpoint-only short program has already passed by termination. The signal was inverted: the run that worked looked like the one that failed, in the one workflow (no pause, just logging) where `verified` is the agent's only binding feedback. The proxy worker now forwards its initial `setBreakpoints` results to the parent as a `breakpoints_synced` status (the #302 `function_breakpoints_synced` pattern), matched to the store by an echoed breakpoint id — immune to path canonicalization, stamped before the program can exit. The live re-sync stays as a belt-and-braces pass (#439)
- **`get_local_variables` truncation summary describes the returned payload, not discarded scopes** — the multi-scope fan-out counted values cut while fetching scopes the adapter policy then threw away (Global/Closure), so a response with 2 complete variables could claim "78 value(s) cut … flagged truncated:true" — training agents to distrust values that were fine and to burn turns re-fetching data that was never truncated. Per-scope truncation is now attributed to the scope(s) whose variables actually reached the caller (genuine cuts in the returned scope are still counted); `scopesSkipped` keeps reporting fan-out work not done. The notice's `Pass names:` example is also derived from the actually-returned variables (truncated ones first) instead of a hardcoded `["a","b"]` that read as leaked state from another session (#438)
- **`expectedContent` no longer warns when the line merely carries a trailing comment** — supplying the entire code of a line whose source ends in a `//` or `#` comment used to trip the "matched as a substring … not the whole line" advisory, contradicting the documented contract that trailing comments are ignored and training callers to distrust the channel meant to catch real off-by-one errors. A new `comment-stripped-exact` match tier (tried before the generic substring pass, only when the expectation itself carries no comment marker — so lines differing inside string literals like `"http://a"` vs `"http://b"` still warn) classifies the whole-code match as exact-equivalent, with no warning (#440)
- **A paused session can no longer answer `get_stack_trace` with a silent empty success** — some adapters report a stop before the stack is materialized (netcoredbg right after the post-attach pause — the milder sibling of #353's `0x80131302`), and the tracked thread can be a frameless runtime thread. The agent-facing stack path now retries an empty-but-successful stackTrace within a bounded window (~3s, exits on first frame), falls back to scanning the other stopped threads and adopts the first one with frames (annotated via a `note` in the response, so scopes/evaluate anchor correctly), and — if everything stays frameless — returns the empty result with a `note` saying what to try instead. Not-paused and no-known-thread empty results now carry an explanatory `note` too

### Added
- **Turnkey Kubernetes debugging recipe** — new [docs/kubernetes.md](docs/kubernetes.md) operational reference (pattern decision table, worked port-forward and ephemeral-sidecar examples, attach path rules, security notes, EKS notes) plus registry-free example manifests under [examples/kubernetes/](examples/kubernetes/) — one self-contained YAML per attach-capable language built from stock public images with the app source in a ConfigMap (initContainer compiles for java/cpp), so `kubectl apply` works identically on kind and managed clusters with no image build or registry — and per-language copy-paste [attach presets](examples/kubernetes/attach-presets.md). Verified end-to-end on kind and Amazon EKS with the published `debugmcp/mcp-debugger:latest` sidecar. Also corrects the JIT-diagnostics tutorial's Part 1 attach call: `localRoot`/`remoteRoot` were silently dropped by the python adapter (no path-mapping lever exists) — breakpoints on attach use debuggee-side paths (#424)
- **CodeLLDB ships as per-platform npm packages (esbuild pattern)** — five new packages `@debugmcp/codelldb-{win32-x64,darwin-x64,darwin-arm64,linux-x64,linux-arm64}` (versioned by the CodeLLDB release, currently 1.11.8, payload staged from the digest-pinned VSIXs) are `optionalDependencies` of `@debugmcp/mcp-debugger`, so npm installs exactly the one matching your os/cpu. Rust and C/C++ debugging now work out of the box on every platform npm serves — previously the CLI tarball bundled linux-x64 only — and the core tarball shrinks from ~54 MB to a few MB. The resolver probes the installed platform package last — after the vendor tree and after `CODELLDB_PATH` — so an explicit `CODELLDB_PATH` still overrides the auto-installed package, and installs with `--omit=optional` keep working via `CODELLDB_PATH` (#383)

### Fixed
- **Startup orphan reapers no longer spike memory on hosts with many processes** — both reapers walked `/proc` with an unbounded `Promise.all` over every pid, so a host with ~1600 processes held that many pending promises and read buffers at once while the libuv threadpool retired four at a time. The resulting peak survived for the whole process lifetime, because V8 does not hand grown arenas back to the OS. Reads are now capped at 32 in flight: measured on a 1600-process Linux host the two reapers add 32 MB instead of 203 MB and finish faster (254 ms vs 377 ms), which takes a freshly started stdio server from 287 MB to 190 MB RSS
- **Docker image builds vendor only the image's own platform** — the builder stage now sets `CODELLDB_VENDOR_ALL=false`, so the prebuild vendor step reuses the digest-verified engine the Dockerfile already fetched instead of re-downloading ~450 MB of win32/darwin CodeLLDB payloads the Linux image never uses (the `.dockerignore` excludes them from the context, so every fresh build paid that download and could fail on any network blip)
- **vendor-codelldb.js can no longer die silently with exit 0** — the script-level root cause behind #389 (the Docker workaround shipped in v0.24.2 stands): a stalled extract-zip promise drained the event loop and Node exited 0 with no failure output. Extraction now runs under a watchdog (default 120 s, `CODELLDB_EXTRACT_TIMEOUT_MS`) whose pending timer keeps the event loop alive and converts a stall into a normal retry/failure, and a premature-exit guard forces exit code 1 with a requested/completed/unresolved-platforms diagnostic if the process would otherwise exit 0 before vendoring finished (#389)
- **Stranded js-debug instances are reaped** — vsDebugServer processes are tagged at spawn via an argv contract and swept on startup when their owning server is gone, closing the orphan gap a SIGKILLed server left behind (#431); a dangling-rejection race in the proxy (the seed-1038859894 flake) is also fixed (#420)
- **HTTP mode: per-session loggers detach from the shared file transport on stop()** — a stopped session could previously close the transport out from under concurrent sessions (#412)
- **Bounded accumulation hardening** — the DAP frame decoder caps frame size and header search (linear accumulation, `DAP_MAX_FRAME_BYTES`), and the remaining unbounded proxy-worker collections are bounded (#413, #414)
- **Thread-anchor guard on `threads` responses + honest `tool:response` logging** — a threads response can no longer clobber the current-thread anchor mid-pause, and the tool-response log's `success` now mirrors the payload's own flag (#407)
- **CI: the Test Summary gate fails when any needed job did not succeed** (previously a skipped job could pass the summary) (#427); vendor outputs no longer embed wall-clock timestamps, keeping shipped bundles reproducible (#428)

### Security
- **Release attestation extended to the Docker image and PyPI launcher** — the docker image publishes a signed provenance attestation and the PyPI launcher ships PEP 740 attestations, alongside the existing npm sigstore provenance and SBOMs (#430)

## [0.24.2] - 2026-08-19

Expand Down
Loading
Loading