dap-mcp-server 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/SKILL.md +175 -28
- package/TODO.md +32 -0
- package/build/dap/types.d.ts +12 -7
- package/build/server.js +197 -34
- package/build/server.js.map +1 -1
- package/build/session/inspect.d.ts +64 -0
- package/build/session/inspect.js +103 -0
- package/build/session/inspect.js.map +1 -0
- package/build/session/run.d.ts +52 -0
- package/build/session/run.js +103 -0
- package/build/session/run.js.map +1 -0
- package/build/session/session.d.ts +8 -9
- package/build/session/session.js +57 -7
- package/build/session/session.js.map +1 -1
- package/package.json +1 -1
- package/src/dap/types.ts +13 -7
- package/src/integration/debug-lifecycle.test.ts +21 -0
- package/src/server.ts +220 -34
- package/src/session/inspect.test.ts +126 -0
- package/src/session/inspect.ts +147 -0
- package/src/session/run.test.ts +74 -0
- package/src/session/run.ts +136 -0
- package/src/session/session.test.ts +42 -0
- package/src/session/session.ts +58 -19
- package/src/test-fixtures/mock-adapter.js +23 -1
- package/src/test-fixtures/no-stop-adapter.js +77 -0
package/README.md
CHANGED
|
@@ -24,12 +24,15 @@ Additional adapters (e.g. Node.js via js-debug) can be registered via the `confi
|
|
|
24
24
|
|
|
25
25
|
| Tool | Description |
|
|
26
26
|
|------|-------------|
|
|
27
|
-
| `
|
|
27
|
+
| `debug_inspect` | **One-shot:** launch → run to breakpoint → evaluate expressions → disconnect. Use first when you just need a value at a line. |
|
|
28
|
+
| `debug_run` | **One-shot:** launch → run to completion → return all output. Combine with logpoints for printf-style debugging without editing source. |
|
|
29
|
+
| `debug_launch` | Start an interactive debug session (launch a program with breakpoints) |
|
|
28
30
|
| `debug_attach` | Attach to a running process |
|
|
29
31
|
| `debug_restart` | Restart with the same configuration |
|
|
30
32
|
| `debug_disconnect` | End a debug session |
|
|
31
|
-
| `set_breakpoints` | Set/replace breakpoints in a file |
|
|
33
|
+
| `set_breakpoints` | Set/replace breakpoints in a file (use `logMessage` for logpoints) |
|
|
32
34
|
| `set_exception_breakpoints` | Break on exceptions |
|
|
35
|
+
| `list_breakpoints` | List currently-registered breakpoints for a session |
|
|
33
36
|
| `get_state` | Get threads, stack trace, scopes, and variables |
|
|
34
37
|
| `evaluate` | Evaluate an expression in the debuggee context |
|
|
35
38
|
| `step` | Continue, next, stepIn, stepOut, or pause |
|
package/SKILL.md
CHANGED
|
@@ -7,56 +7,203 @@ description: Use when debugging a program — when you need to set breakpoints,
|
|
|
7
7
|
|
|
8
8
|
You have access to a real debugger via the `dap` MCP server. **Use it instead of adding print/log statements** whenever you need to understand runtime behavior, track down a bug, or verify a fix.
|
|
9
9
|
|
|
10
|
-
## When to
|
|
10
|
+
## When to switch from print() to the debugger
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- You need to inspect the value of variables at a specific point in execution
|
|
14
|
-
- You suspect a logic error in a loop, conditional, or data transformation
|
|
15
|
-
- A function returns an unexpected result and you want to trace the execution path
|
|
16
|
-
- You want to verify your fix actually changes behavior at the right point
|
|
12
|
+
Reach for the debugger — don't keep guessing — when any of these are true:
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
- **Your first fix attempt was wrong.** Stop iterating on guesses. Inspect actual values.
|
|
15
|
+
- **You're about to add 3+ print statements.** Use a logpoint instead (see below) — same information, no code edits.
|
|
16
|
+
- **You need a value on iteration N of a loop.** Use `hitCondition: "N"` instead of conditional prints.
|
|
17
|
+
- **A test fails with a non-obvious traceback.** Launch the test under the debugger; you'll see locals at the failure site.
|
|
18
|
+
- **You're reasoning about state that's modified across many call sites.** A conditional breakpoint on the value beats grepping for assignments.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
Print debugging is fine for: trivial one-line checks, scripts you'll throw away in the next message, or when you genuinely just want to see "did this code path execute?".
|
|
21
|
+
|
|
22
|
+
## Logpoints — print debugging without editing code
|
|
23
|
+
|
|
24
|
+
A **logpoint** is a breakpoint that doesn't pause execution. It just emits a formatted message every time it's hit. This is the single most useful feature for AI agents.
|
|
25
|
+
|
|
26
|
+
Set it via the `logMessage` field on a breakpoint:
|
|
22
27
|
|
|
23
28
|
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
program: "/absolute/path/to/file.py",
|
|
27
|
-
stopOnEntry: false,
|
|
29
|
+
set_breakpoints({
|
|
30
|
+
file: "/abs/path/src/foo.py",
|
|
28
31
|
breakpoints: [
|
|
29
|
-
{
|
|
32
|
+
{ line: 42, logMessage: "x={x} y={y} state={self.state}" },
|
|
33
|
+
{ line: 88, logMessage: "iteration {i}: items={len(items)}" }
|
|
30
34
|
]
|
|
31
35
|
})
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
Then `step({ action: "continue" })` and let the program run. Retrieve every log with `get_output`. You get the same information as inserting `print(f"x={x} y={y}")` at five places — without touching the source file, without re-running, and with the program's natural execution preserved.
|
|
39
|
+
|
|
40
|
+
**When to use logpoints over breakpoints**: when you want to *observe* values across many hits rather than *interrupt* execution to inspect one. They are the right tool ~70% of the time.
|
|
41
|
+
|
|
42
|
+
Expression syntax inside `{...}` depends on the adapter (debugpy: Python; node: JS). The expression is evaluated in the breakpoint's scope.
|
|
43
|
+
|
|
44
|
+
## Debugging a failing test
|
|
45
|
+
|
|
46
|
+
This is the dominant case. Use the test runner as the program, with the specific test as an argument.
|
|
47
|
+
|
|
48
|
+
### pytest
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
debug_launch({
|
|
52
|
+
adapter: "python",
|
|
53
|
+
program: "/abs/path/.venv/bin/pytest",
|
|
54
|
+
args: ["tests/test_foo.py::test_bar", "-x", "--no-header"],
|
|
55
|
+
cwd: "/abs/path",
|
|
56
|
+
breakpoints: [{ file: "/abs/path/src/foo.py", line: 42 }],
|
|
57
|
+
stopOnEntry: false,
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For pytest, also consider setting `exceptionBreakpoints: ["raised"]` — pytest swallows exceptions before you see them, but the debugger will stop at the raise site.
|
|
62
|
+
|
|
63
|
+
### vitest / jest (Node.js)
|
|
64
|
+
|
|
65
|
+
Requires the `node` adapter configured (see "Node.js setup" below). Use the runner binary from `node_modules/.bin`:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
debug_launch({
|
|
69
|
+
adapter: "node",
|
|
70
|
+
program: "${cwd}/node_modules/.bin/vitest",
|
|
71
|
+
args: ["run", "tests/foo.test.ts", "-t", "the failing test name"],
|
|
72
|
+
cwd: "/abs/path",
|
|
73
|
+
breakpoints: [{ file: "/abs/path/src/foo.ts", line: 42 }],
|
|
74
|
+
stopOnEntry: false,
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### go test
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
debug_launch({
|
|
82
|
+
adapter: "go",
|
|
83
|
+
program: "${cwd}", // delve takes a package dir, not a binary
|
|
84
|
+
args: ["-test.run", "TestFoo", "-test.v"],
|
|
85
|
+
cwd: "/abs/path",
|
|
86
|
+
breakpoints: [{ file: "/abs/path/foo.go", line: 42 }],
|
|
87
|
+
stopOnEntry: false,
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Delve uses `dlv test` semantics under the hood when the program is a package directory containing test files.
|
|
92
|
+
|
|
93
|
+
### Node.js setup
|
|
94
|
+
|
|
95
|
+
If `list_adapters` shows no `node` entry, register it once (path depends on installed extension):
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
configure_adapter({
|
|
99
|
+
type: "node",
|
|
100
|
+
runtime: "node",
|
|
101
|
+
program: "/abs/path/to/js-debug/src/dapDebugServer.js",
|
|
102
|
+
args: [],
|
|
103
|
+
scope: "global",
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`js-debug-dap` ships with the VS Code Node debugger extension. Alternatively use `@vscode/js-debug` from npm.
|
|
108
|
+
|
|
109
|
+
## Quick Start (single-file program)
|
|
110
|
+
|
|
111
|
+
1. `list_adapters` — see what's installed.
|
|
112
|
+
2. Launch with breakpoints in one call:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
debug_launch({
|
|
116
|
+
adapter: "python",
|
|
117
|
+
program: "/abs/path/file.py",
|
|
118
|
+
stopOnEntry: false,
|
|
119
|
+
breakpoints: [{ file: "/abs/path/file.py", line: 42 }],
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
3. The response already contains the full state (threads, stack, scopes, variables) when the breakpoint is hit. No follow-up `get_state` needed.
|
|
124
|
+
4. Step with `step({ action: "next" })` — response includes the new state.
|
|
125
|
+
5. `evaluate({ expression: "len(items)" })` for ad-hoc inspection.
|
|
126
|
+
6. `debug_disconnect` when done.
|
|
127
|
+
|
|
128
|
+
## Stepping strategy
|
|
129
|
+
|
|
130
|
+
- **`next`** (step over) is the default choice. Use it unless you have a specific reason not to.
|
|
131
|
+
- **`stepIn`** only when you suspect the bug is *inside* the function being called — not at the call site. Stepping into library code will get you lost fast.
|
|
132
|
+
- **`stepOut`** to bail out of a function you stepped into by mistake.
|
|
133
|
+
- **`continue`** to run to the next breakpoint. With logpoints set, this is how you collect runtime traces.
|
|
134
|
+
|
|
135
|
+
If you find yourself stepping line-by-line for more than ~10 steps, you're probably doing it wrong. Set a breakpoint at the next interesting location and continue to it.
|
|
136
|
+
|
|
137
|
+
## Worked example
|
|
138
|
+
|
|
139
|
+
Bug: `compute_total(items)` returns `0` when items contain a tax-exempt entry.
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
# Launch under pytest with a breakpoint at the suspect line
|
|
143
|
+
debug_launch({
|
|
144
|
+
adapter: "python",
|
|
145
|
+
program: "/proj/.venv/bin/pytest",
|
|
146
|
+
args: ["tests/test_billing.py::test_tax_exempt", "-x"],
|
|
147
|
+
cwd: "/proj",
|
|
148
|
+
breakpoints: [{ file: "/proj/src/billing.py", line: 57 }],
|
|
149
|
+
stopOnEntry: false,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
# Response (paraphrased):
|
|
153
|
+
# {
|
|
154
|
+
# sessionId: "s-1",
|
|
155
|
+
# status: "stopped",
|
|
156
|
+
# location: { file: ".../billing.py", line: 57, functionName: "compute_total" },
|
|
157
|
+
# scopes: [{ name: "Locals", variables: [
|
|
158
|
+
# { name: "items", value: "[{...}, {...}]", ... },
|
|
159
|
+
# { name: "subtotal", value: "100", ... },
|
|
160
|
+
# { name: "tax", value: "0", ... }
|
|
161
|
+
# ] }]
|
|
162
|
+
# }
|
|
163
|
+
|
|
164
|
+
# Inspect the exempt item more closely
|
|
165
|
+
evaluate({ expression: "[i for i in items if i.get('tax_exempt')]" })
|
|
166
|
+
# → [{name: 'book', price: 100, tax_exempt: True}]
|
|
167
|
+
|
|
168
|
+
# Step over to see what compute_total actually returns
|
|
169
|
+
step({ action: "next" })
|
|
170
|
+
# → see that `total = subtotal + tax` returns 100, not 0. Bug is upstream.
|
|
171
|
+
|
|
172
|
+
# Continue to verify nothing else changes the return
|
|
173
|
+
step({ action: "continue" })
|
|
174
|
+
|
|
175
|
+
debug_disconnect()
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Fix locally, re-run the test, done.
|
|
38
179
|
|
|
39
180
|
## Key Rules
|
|
40
181
|
|
|
41
182
|
- **Do NOT call `get_state` after `step`** — `step` already returns the new stopped state.
|
|
42
|
-
- **Omit `sessionId`** when only one debug session is active
|
|
183
|
+
- **Omit `sessionId`** when only one debug session is active.
|
|
43
184
|
- **Use absolute paths** for `program` and breakpoint `file` parameters.
|
|
44
|
-
- **Prefer `stopOnEntry: false` with breakpoints**
|
|
45
|
-
- **
|
|
185
|
+
- **Prefer `stopOnEntry: false` with breakpoints** — it gets you directly to the interesting code.
|
|
186
|
+
- **Use `get_output`** for program stdout/stderr (including logpoint output).
|
|
46
187
|
|
|
47
188
|
## Debugging Strategies
|
|
48
189
|
|
|
49
190
|
### Narrowing down a bug
|
|
50
|
-
1. Set a breakpoint where
|
|
51
|
-
2. Set another
|
|
52
|
-
3. Use `step` with `
|
|
53
|
-
4. Use `evaluate` to check intermediate values.
|
|
191
|
+
1. Set a breakpoint where state is known good.
|
|
192
|
+
2. Set another where state is wrong.
|
|
193
|
+
3. Use `step` with `next` to walk between them, or set intermediate logpoints.
|
|
54
194
|
|
|
55
195
|
### Understanding a crash
|
|
56
|
-
1. Launch with `exceptionBreakpoints: ["uncaughtExceptions"]
|
|
57
|
-
2. When
|
|
196
|
+
1. Launch with `exceptionBreakpoints: ["uncaughtExceptions"]` (or `"raised"` for any raise).
|
|
197
|
+
2. When stopped at the exception, inspect stack and locals.
|
|
58
198
|
|
|
59
199
|
### Verifying a fix
|
|
60
200
|
1. Set a breakpoint at the fixed code.
|
|
61
|
-
2. Run to it, inspect variables, confirm
|
|
201
|
+
2. Run to it, inspect variables, confirm new behavior.
|
|
62
202
|
3. Continue to verify the program completes successfully.
|
|
203
|
+
|
|
204
|
+
### Observing a hot loop without stopping it
|
|
205
|
+
1. Set logpoints at the relevant lines with `{i}` and any state expression.
|
|
206
|
+
2. `continue`.
|
|
207
|
+
3. Read the trace via `get_output`. For long-running programs, poll incrementally:
|
|
208
|
+
pass the `nextSince` value from each call as the `since` parameter of the next call
|
|
209
|
+
to receive only newly-emitted entries.
|
package/TODO.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# DAP MCP Server — Improvement TODO
|
|
2
|
+
|
|
3
|
+
Findings from a self-review of how usable this MCP is for an AI agent. Ordered by priority (descending). Check items off as completed.
|
|
4
|
+
|
|
5
|
+
## Tier 1 — Documentation (SKILL.md). High leverage, low risk.
|
|
6
|
+
|
|
7
|
+
- [x] **P1 — Logpoints section.** `breakpoints[].logMessage` is already wired through (session.ts:138) but is invisible in SKILL.md. It is the closest thing to a drop-in replacement for `print()` debugging: no code edits, captures every hit, no execution pause. Should have its own headline section with an example.
|
|
8
|
+
- [x] **P2 — "Debug a failing test" section.** Most real bugs surface inside test runners (pytest, vitest, jest, go test, cargo test). Currently zero guidance. Add a per-framework template for launching a single test under the debugger.
|
|
9
|
+
- [x] **P3 — "Switch from print() to debugger" triggers.** Concrete rules an AI agent will actually follow: "if first fix attempt was wrong, stop guessing"; "if about to add 3+ prints, use logpoints"; "if reasoning about iteration N, use `hitCondition`"; "if traceback is non-obvious, launch test under debugger".
|
|
10
|
+
- [x] **P4 — Stepping strategy.** Default to `next`. Use `stepIn` only when you suspect the bug is *inside* the called function. Otherwise agents get lost in library internals.
|
|
11
|
+
- [x] **P5 — Full worked example.** Real bug → real `debug_launch` call → real returned state → identified fix. Replaces all the abstract advice with one concrete walkthrough.
|
|
12
|
+
- [x] **P5b — Node.js adapter recipe.** Builtin was removed; add a ready-to-paste `configure_adapter` snippet for `js-debug-dap`.
|
|
13
|
+
|
|
14
|
+
## Tier 2 — Code changes that materially improve UX.
|
|
15
|
+
|
|
16
|
+
- [x] **P6 — `debug_inspect` macro tool.** One call: launch, run to a breakpoint, evaluate a list of expressions, disconnect. Collapses a 5–7 round-trip workflow into 1. Most likely change to make me reach for the debugger reflexively.
|
|
17
|
+
- [x] **P7 — Flip `stopOnEntry` default to `false` when breakpoints are provided.** Today defaults to `true` regardless (server.ts:206). Doing the right thing automatically is better than relying on the agent reading the SKILL.md.
|
|
18
|
+
- [x] **P8 — Structured deep variables.** `getVariables` with `depth > 1` flattens children into the value string (session.ts:271–278). Hard for the AI to consume. Return nested `children: [...]` instead.
|
|
19
|
+
- [x] **P9 — `list_adapters` summary line + surfaced install hints.** Add `summary: "2/4 installed"` and lift `installHint` for missing adapters to the top level so it's scannable.
|
|
20
|
+
- [x] **P10 — `debug_run` macro tool.** Launch, run to completion, return all stdout/stderr. Zero breakpoints, zero round-trips. For "what does this script actually print/crash with?" cases.
|
|
21
|
+
|
|
22
|
+
## Tier 3 — Polish.
|
|
23
|
+
|
|
24
|
+
- [x] **P11 — Better "session is not stopped" error.** Currently says "use step with action: pause" (server.ts:549) — bad advice (Python `pause` lands in arbitrary library code). Suggest "set a breakpoint where you want to inspect, then continue to it".
|
|
25
|
+
- [x] **P12 — Version drift.** `McpServer` registers as `0.1.0` (server.ts:48); package.json is `0.1.10`. Read from `package.json`.
|
|
26
|
+
- [x] **P13 — `list_breakpoints` tool.** After several `set_breakpoints` calls, no way to introspect what's set. Minor.
|
|
27
|
+
|
|
28
|
+
## Tier 4 — Speculative.
|
|
29
|
+
|
|
30
|
+
- [x] **`get_output({ since })` for incremental polling.** Smaller substitute for streaming subscriptions. Caller passes the `nextSince` value from a prior call to receive only newly-emitted entries. Useful for watching logpoint output during `continue` against long-running programs.
|
|
31
|
+
- [ ] ~~Streaming output via MCP resource subscriptions.~~ AI agents don't watch streams in real time; the request/response pattern doesn't benefit. `since`-based polling covers the practical case.
|
|
32
|
+
- [ ] ~~Auto-detect test runner from program path.~~ Too magic; SKILL.md recipes (P2) make the right call explicit, and silent misfires would be worse than no detection at all.
|
package/build/dap/types.d.ts
CHANGED
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
* SOFTWARE.
|
|
21
21
|
*/
|
|
22
22
|
export type SessionState = "idle" | "configuring" | "running" | "stopped" | "terminated";
|
|
23
|
+
export interface Variable {
|
|
24
|
+
name: string;
|
|
25
|
+
value: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
hasChildren: boolean;
|
|
28
|
+
variablesReference: number;
|
|
29
|
+
/** Populated when getVariables is called with depth > 1 and this variable has children. */
|
|
30
|
+
children?: Variable[];
|
|
31
|
+
}
|
|
23
32
|
export interface StoppedState {
|
|
24
33
|
status: "stopped";
|
|
25
34
|
reason: string;
|
|
@@ -43,13 +52,7 @@ export interface StoppedState {
|
|
|
43
52
|
}>;
|
|
44
53
|
scopes: Array<{
|
|
45
54
|
name: string;
|
|
46
|
-
variables:
|
|
47
|
-
name: string;
|
|
48
|
-
value: string;
|
|
49
|
-
type?: string;
|
|
50
|
-
hasChildren: boolean;
|
|
51
|
-
variablesReference: number;
|
|
52
|
-
}>;
|
|
55
|
+
variables: Variable[];
|
|
53
56
|
}>;
|
|
54
57
|
output?: string[];
|
|
55
58
|
}
|
|
@@ -68,6 +71,8 @@ export interface AdapterConfig {
|
|
|
68
71
|
installHint?: string;
|
|
69
72
|
}
|
|
70
73
|
export interface OutputEntry {
|
|
74
|
+
/** Monotonic per-session sequence number assigned at push time. Survives ring-buffer eviction. */
|
|
75
|
+
seq: number;
|
|
71
76
|
category: string;
|
|
72
77
|
text: string;
|
|
73
78
|
timestamp: string;
|
package/build/server.js
CHANGED
|
@@ -23,10 +23,24 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
23
23
|
import { z } from "zod";
|
|
24
24
|
import os from "node:os";
|
|
25
25
|
import path from "node:path";
|
|
26
|
+
import { readFileSync } from "node:fs";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
26
28
|
import { AdapterRegistry } from "./adapters/registry.js";
|
|
27
29
|
import { SessionManager } from "./session/session-manager.js";
|
|
28
30
|
import { DebugSession } from "./session/session.js";
|
|
31
|
+
import { runDebugInspect } from "./session/inspect.js";
|
|
32
|
+
import { runDebugRun } from "./session/run.js";
|
|
29
33
|
import { checkAdapterAvailable } from "./adapters/detect.js";
|
|
34
|
+
function readPackageVersion() {
|
|
35
|
+
try {
|
|
36
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const pkg = JSON.parse(readFileSync(path.join(here, "..", "package.json"), "utf-8"));
|
|
38
|
+
return pkg.version ?? "0.0.0";
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return "0.0.0";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
30
44
|
export function createServer() {
|
|
31
45
|
const globalDir = path.join(os.homedir(), ".dap-mcp");
|
|
32
46
|
const projectDir = path.join(process.cwd(), ".dap-mcp");
|
|
@@ -38,46 +52,49 @@ export function createServer() {
|
|
|
38
52
|
});
|
|
39
53
|
const server = new McpServer({
|
|
40
54
|
name: "dap-mcp-server",
|
|
41
|
-
version:
|
|
55
|
+
version: readPackageVersion(),
|
|
42
56
|
}, {
|
|
43
57
|
instructions: [
|
|
44
58
|
"This server provides AI-driven debugging via the Debug Adapter Protocol (DAP).",
|
|
45
59
|
"",
|
|
46
|
-
"##
|
|
60
|
+
"## When to use which tool",
|
|
61
|
+
"",
|
|
62
|
+
"- **`debug_inspect`** — One-shot 'what is X at line N?'. Launches, hits a breakpoint, evaluates expressions,",
|
|
63
|
+
" disconnects. Use this first; it collapses a multi-call workflow into a single call.",
|
|
64
|
+
"- **`debug_run`** — Run a program with no stops and collect all output. Combine with `logpoints` (breakpoints",
|
|
65
|
+
" with `logMessage`) for printf-style debugging without modifying source.",
|
|
66
|
+
"- **`debug_launch` + step/evaluate** — Interactive multi-step session when you need to explore.",
|
|
47
67
|
"",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"4. `step` —
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"7. `
|
|
59
|
-
"8. `
|
|
68
|
+
"## Interactive Debugging Workflow",
|
|
69
|
+
"",
|
|
70
|
+
"1. `list_adapters` — discover available adapters; the response includes a summary and install hints.",
|
|
71
|
+
"2. `debug_launch` (or `debug_attach`) — start a session. With breakpoints provided, `stopOnEntry` defaults",
|
|
72
|
+
" to false so the program runs to the first breakpoint. Returns sessionId + initial stopped state.",
|
|
73
|
+
"3. Inspect state with `get_state` — threads + call stack + scopes + variables in one call.",
|
|
74
|
+
"4. `step` — continue, next, stepIn, stepOut, pause. Blocks until stopped; returns new state directly.",
|
|
75
|
+
"5. `evaluate` — expressions in the context of a stopped frame.",
|
|
76
|
+
"6. `set_breakpoints` / `set_exception_breakpoints` — modify breakpoints mid-session. Use `logMessage`",
|
|
77
|
+
" on a breakpoint to make it a logpoint (emits formatted text without pausing).",
|
|
78
|
+
"7. `list_breakpoints` — see what's currently set.",
|
|
79
|
+
"8. `get_output` — retrieve stdout/stderr/console output (10K-line ring buffer).",
|
|
80
|
+
"9. `debug_disconnect` — end. `debug_restart` replays the launch config with preserved breakpoints.",
|
|
60
81
|
"",
|
|
61
82
|
"## Key Conventions",
|
|
62
83
|
"",
|
|
63
|
-
"- **Auto-session resolution**:
|
|
64
|
-
"
|
|
65
|
-
"- **
|
|
66
|
-
"
|
|
67
|
-
"- **
|
|
68
|
-
"
|
|
69
|
-
"-
|
|
70
|
-
"
|
|
71
|
-
"- **Adapter registration**: Use `configure_adapter` to register adapters. Configs are persisted to",
|
|
72
|
-
" `~/.dap-mcp/adapters.json` (global) or `./.dap-mcp/adapters.json` (project), with project overriding global.",
|
|
73
|
-
"- **Error handling**: All tool errors are returned as `isError: true` results with an error message.",
|
|
74
|
-
" The session remains usable after most errors.",
|
|
84
|
+
"- **Auto-session resolution**: tools accept an optional `sessionId`; with one active session, omit it.",
|
|
85
|
+
"- **Synchronous stepping**: `step` returns the new stopped state. Don't follow it with `get_state`.",
|
|
86
|
+
"- **Variable depth**: `get_state` accepts `variableDepth` (default 1). With `> 1`, container variables",
|
|
87
|
+
" expose their entries as a structured `children` array.",
|
|
88
|
+
"- **stopOnEntry default**: false when `debug_launch` is given breakpoints, true otherwise.",
|
|
89
|
+
"- **Adapter registration**: `configure_adapter` persists to `~/.dap-mcp/adapters.json` (global) or",
|
|
90
|
+
" `./.dap-mcp/adapters.json` (project); project overrides global.",
|
|
91
|
+
"- **Error handling**: tool errors return `isError: true`. The session usually remains usable.",
|
|
75
92
|
].join("\n"),
|
|
76
93
|
});
|
|
77
94
|
// ────────────────────────────────────────────────────────────────────────────
|
|
78
95
|
// 1. list_adapters
|
|
79
96
|
// ────────────────────────────────────────────────────────────────────────────
|
|
80
|
-
server.tool("list_adapters", "List all known debug adapters with their availability status on this machine.", {}, async () => {
|
|
97
|
+
server.tool("list_adapters", "List all known debug adapters with their availability status on this machine. Returns a summary line, the full adapter list, and a separate 'missing' list with installation hints for adapters that aren't installed.", {}, async () => {
|
|
81
98
|
try {
|
|
82
99
|
const adapters = registry.listAll();
|
|
83
100
|
const results = await Promise.all(adapters.map(async (a) => {
|
|
@@ -85,8 +102,20 @@ export function createServer() {
|
|
|
85
102
|
const available = cmd ? await checkAdapterAvailable(cmd) : false;
|
|
86
103
|
return { ...a, available };
|
|
87
104
|
}));
|
|
105
|
+
const available = results.filter((r) => r.available);
|
|
106
|
+
const missing = results
|
|
107
|
+
.filter((r) => !r.available)
|
|
108
|
+
.map((r) => ({ type: r.type, installHint: r.installHint }));
|
|
109
|
+
const summary = missing.length === 0
|
|
110
|
+
? `${available.length}/${results.length} adapters available`
|
|
111
|
+
: `${available.length}/${results.length} adapters available; missing: ${missing.map((m) => m.type).join(", ")}`;
|
|
88
112
|
return {
|
|
89
|
-
content: [
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: JSON.stringify({ summary, adapters: results, missing }, null, 2),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
90
119
|
};
|
|
91
120
|
}
|
|
92
121
|
catch (err) {
|
|
@@ -138,7 +167,10 @@ export function createServer() {
|
|
|
138
167
|
args: z.array(z.string()).optional().describe("Program arguments"),
|
|
139
168
|
cwd: z.string().optional().describe("Working directory for the program"),
|
|
140
169
|
env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
|
|
141
|
-
stopOnEntry: z
|
|
170
|
+
stopOnEntry: z
|
|
171
|
+
.boolean()
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("Stop on program entry. Defaults to false when breakpoints are provided (run to first breakpoint), true otherwise."),
|
|
142
174
|
breakpoints: z
|
|
143
175
|
.array(z.object({
|
|
144
176
|
file: z.string().describe("Source file path"),
|
|
@@ -165,7 +197,8 @@ export function createServer() {
|
|
|
165
197
|
.optional()
|
|
166
198
|
.describe("Override adapter config inline instead of looking up from registry"),
|
|
167
199
|
}, async ({ adapter, program, args, cwd, env, stopOnEntry, breakpoints, exceptionBreakpoints, adapterConfig, }) => {
|
|
168
|
-
|
|
200
|
+
// Default: if breakpoints are provided, run to the first one; otherwise stop at entry.
|
|
201
|
+
const shouldStop = stopOnEntry ?? !(breakpoints !== undefined && breakpoints.length > 0);
|
|
169
202
|
let session;
|
|
170
203
|
try {
|
|
171
204
|
const config = adapterConfig ??
|
|
@@ -463,7 +496,7 @@ export function createServer() {
|
|
|
463
496
|
content: [
|
|
464
497
|
{
|
|
465
498
|
type: "text",
|
|
466
|
-
text: `Error: Session is not stopped (current state: ${session.state}).
|
|
499
|
+
text: `Error: Session is not stopped (current state: ${session.state}). Expressions can only be evaluated at a stopped frame. Set a breakpoint where you want to inspect (set_breakpoints) and continue to it (step with action 'continue'), then retry.`,
|
|
467
500
|
},
|
|
468
501
|
],
|
|
469
502
|
isError: true,
|
|
@@ -575,17 +608,21 @@ export function createServer() {
|
|
|
575
608
|
// ────────────────────────────────────────────────────────────────────────────
|
|
576
609
|
// 10. get_output
|
|
577
610
|
// ────────────────────────────────────────────────────────────────────────────
|
|
578
|
-
server.tool("get_output", "Get buffered stdout/stderr and debug adapter output for a session.", {
|
|
611
|
+
server.tool("get_output", "Get buffered stdout/stderr and debug adapter output for a session. Pass `since` (the `nextSince` from a previous call) to get only newly-emitted entries — useful for polling logpoint output without re-reading the full buffer.", {
|
|
579
612
|
sessionId: z.string().optional().describe("Session ID (omit if only one session is active)"),
|
|
580
613
|
category: z
|
|
581
614
|
.string()
|
|
582
615
|
.optional()
|
|
583
616
|
.describe("Filter by output category: 'stdout', 'stderr', 'console', etc."),
|
|
584
617
|
clear: z.boolean().optional().describe("Clear the output buffer after reading (default: false)"),
|
|
585
|
-
|
|
618
|
+
since: z
|
|
619
|
+
.number()
|
|
620
|
+
.optional()
|
|
621
|
+
.describe("Return only entries with seq > since. Pass the `nextSince` value from a prior call to fetch incremental output."),
|
|
622
|
+
}, async ({ sessionId, category, clear, since }) => {
|
|
586
623
|
try {
|
|
587
624
|
const session = sessions.resolve(sessionId);
|
|
588
|
-
const result = session.getOutput(category, clear ?? false);
|
|
625
|
+
const result = session.getOutput(category, clear ?? false, since);
|
|
589
626
|
return {
|
|
590
627
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
591
628
|
};
|
|
@@ -715,6 +752,132 @@ export function createServer() {
|
|
|
715
752
|
}
|
|
716
753
|
});
|
|
717
754
|
// ────────────────────────────────────────────────────────────────────────────
|
|
755
|
+
// 13. debug_inspect
|
|
756
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
757
|
+
server.tool("debug_inspect", "One-shot inspection: launch a program, run to a breakpoint, evaluate a list of expressions in that frame, then disconnect. Collapses launch + wait + evaluate + disconnect into a single call. Use for 'what is X at line N?' debugging.", {
|
|
758
|
+
adapter: z.string().describe("Adapter type to use (e.g. 'python', 'node')"),
|
|
759
|
+
program: z.string().describe("Path to the program to debug"),
|
|
760
|
+
args: z.array(z.string()).optional().describe("Program arguments"),
|
|
761
|
+
cwd: z.string().optional().describe("Working directory for the program"),
|
|
762
|
+
env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
|
|
763
|
+
at: z
|
|
764
|
+
.object({
|
|
765
|
+
file: z.string().describe("Absolute path to the source file"),
|
|
766
|
+
line: z.number().describe("Line number (1-based)"),
|
|
767
|
+
condition: z.string().optional().describe("Conditional breakpoint expression"),
|
|
768
|
+
hitCondition: z.string().optional().describe("Hit-count condition (e.g. '5' for 5th hit)"),
|
|
769
|
+
})
|
|
770
|
+
.describe("Breakpoint location to stop at"),
|
|
771
|
+
expressions: z
|
|
772
|
+
.array(z.string())
|
|
773
|
+
.optional()
|
|
774
|
+
.describe("Expressions to evaluate when stopped at the breakpoint. Omit to just verify the line is reached."),
|
|
775
|
+
timeout: z
|
|
776
|
+
.number()
|
|
777
|
+
.optional()
|
|
778
|
+
.describe("Milliseconds to wait for the breakpoint to be hit (default: 30000)"),
|
|
779
|
+
adapterConfig: z
|
|
780
|
+
.object({
|
|
781
|
+
type: z.string(),
|
|
782
|
+
program: z.string(),
|
|
783
|
+
runtime: z.string().optional(),
|
|
784
|
+
args: z.array(z.string()).optional(),
|
|
785
|
+
launchDefaults: z.record(z.string(), z.unknown()).optional(),
|
|
786
|
+
installHint: z.string().optional(),
|
|
787
|
+
})
|
|
788
|
+
.optional()
|
|
789
|
+
.describe("Override adapter config inline"),
|
|
790
|
+
}, async (opts) => {
|
|
791
|
+
try {
|
|
792
|
+
const result = await runDebugInspect(registry, sessions, opts);
|
|
793
|
+
return {
|
|
794
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
catch (err) {
|
|
798
|
+
return {
|
|
799
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
800
|
+
isError: true,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
805
|
+
// 14. list_breakpoints
|
|
806
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
807
|
+
server.tool("list_breakpoints", "List the breakpoints currently registered with a session, grouped by source file. Includes regular breakpoints, conditional breakpoints, and logpoints.", {
|
|
808
|
+
sessionId: z.string().optional().describe("Session ID (omit if only one session is active)"),
|
|
809
|
+
}, async ({ sessionId }) => {
|
|
810
|
+
try {
|
|
811
|
+
const session = sessions.resolve(sessionId);
|
|
812
|
+
const bps = session.getBreakpoints();
|
|
813
|
+
const files = Array.from(bps.entries()).map(([file, breakpoints]) => ({
|
|
814
|
+
file,
|
|
815
|
+
breakpoints,
|
|
816
|
+
}));
|
|
817
|
+
const totalCount = files.reduce((n, f) => n + f.breakpoints.length, 0);
|
|
818
|
+
return {
|
|
819
|
+
content: [
|
|
820
|
+
{
|
|
821
|
+
type: "text",
|
|
822
|
+
text: JSON.stringify({ count: totalCount, files }, null, 2),
|
|
823
|
+
},
|
|
824
|
+
],
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
catch (err) {
|
|
828
|
+
return {
|
|
829
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
830
|
+
isError: true,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
835
|
+
// 15. debug_run
|
|
836
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
837
|
+
server.tool("debug_run", "Launch a program with no stops, let it run to completion, and return all stdout/stderr/console output. Optionally inject logpoints (breakpoints with logMessage) for printf-style debugging without modifying source. Use for 'what does this script actually do?' cases where you don't need to inspect intermediate state.", {
|
|
838
|
+
adapter: z.string().describe("Adapter type to use (e.g. 'python', 'node')"),
|
|
839
|
+
program: z.string().describe("Path to the program to debug"),
|
|
840
|
+
args: z.array(z.string()).optional().describe("Program arguments"),
|
|
841
|
+
cwd: z.string().optional().describe("Working directory for the program"),
|
|
842
|
+
env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
|
|
843
|
+
logpoints: z
|
|
844
|
+
.array(z.object({
|
|
845
|
+
file: z.string().describe("Absolute path to the source file"),
|
|
846
|
+
line: z.number().describe("Line number (1-based)"),
|
|
847
|
+
logMessage: z.string().describe("Message template; expressions in {braces} are evaluated in scope"),
|
|
848
|
+
}))
|
|
849
|
+
.optional()
|
|
850
|
+
.describe("Logpoints to emit per-hit messages without pausing execution"),
|
|
851
|
+
timeout: z
|
|
852
|
+
.number()
|
|
853
|
+
.optional()
|
|
854
|
+
.describe("Total wall-clock timeout for the whole run in milliseconds (default: 60000)"),
|
|
855
|
+
adapterConfig: z
|
|
856
|
+
.object({
|
|
857
|
+
type: z.string(),
|
|
858
|
+
program: z.string(),
|
|
859
|
+
runtime: z.string().optional(),
|
|
860
|
+
args: z.array(z.string()).optional(),
|
|
861
|
+
launchDefaults: z.record(z.string(), z.unknown()).optional(),
|
|
862
|
+
installHint: z.string().optional(),
|
|
863
|
+
})
|
|
864
|
+
.optional()
|
|
865
|
+
.describe("Override adapter config inline"),
|
|
866
|
+
}, async (opts) => {
|
|
867
|
+
try {
|
|
868
|
+
const result = await runDebugRun(registry, sessions, opts);
|
|
869
|
+
return {
|
|
870
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
catch (err) {
|
|
874
|
+
return {
|
|
875
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
876
|
+
isError: true,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
718
881
|
// Resource: dap://sessions
|
|
719
882
|
// ────────────────────────────────────────────────────────────────────────────
|
|
720
883
|
// NOTE: This resource does not emit change notifications.
|