dap-mcp-server 0.1.12 → 0.1.16
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 +25 -7
- package/SKILL.md +37 -186
- package/build/adapters/detect.d.ts +12 -0
- package/build/adapters/detect.js +91 -0
- package/build/adapters/detect.js.map +1 -1
- package/build/adapters/registry.js +35 -12
- package/build/adapters/registry.js.map +1 -1
- package/build/diagnostics/doctor.d.ts +64 -0
- package/build/diagnostics/doctor.js +209 -0
- package/build/diagnostics/doctor.js.map +1 -0
- package/build/server.js +305 -83
- package/build/server.js.map +1 -1
- package/build/session/inspect.d.ts +6 -2
- package/build/session/inspect.js +24 -8
- package/build/session/inspect.js.map +1 -1
- package/build/session/run.js +2 -9
- package/build/session/run.js.map +1 -1
- package/build/session/session.d.ts +16 -0
- package/build/session/session.js +63 -8
- package/build/session/session.js.map +1 -1
- package/integrations/claude/dap-debugging/.claude-plugin/plugin.json +9 -0
- package/integrations/claude/dap-debugging/.mcp.json +8 -0
- package/integrations/claude/dap-debugging/skills/dap-debugging/SKILL.md +49 -0
- package/package.json +8 -3
- package/.forgejo/workflows/publish.yml +0 -63
- package/CLAUDE.md +0 -71
- package/TODO.md +0 -32
- package/docs/plans/2026-03-05-dap-wrapper-mcp-design.md +0 -326
- package/docs/plans/2026-03-05-dap-wrapper-mcp-implementation.md +0 -2815
- package/src/adapters/builtin.ts +0 -75
- package/src/adapters/detect.test.ts +0 -36
- package/src/adapters/detect.ts +0 -36
- package/src/adapters/registry.test.ts +0 -74
- package/src/adapters/registry.ts +0 -86
- package/src/dap/dap-client.test.ts +0 -118
- package/src/dap/dap-client.ts +0 -588
- package/src/dap/protocol.test.ts +0 -82
- package/src/dap/protocol.ts +0 -78
- package/src/dap/ring-buffer.test.ts +0 -82
- package/src/dap/ring-buffer.ts +0 -64
- package/src/dap/types.ts +0 -115
- package/src/index.ts +0 -30
- package/src/integration/debug-lifecycle.test.ts +0 -131
- package/src/server.ts +0 -1096
- package/src/session/inspect.test.ts +0 -126
- package/src/session/inspect.ts +0 -160
- package/src/session/run.test.ts +0 -74
- package/src/session/run.ts +0 -144
- package/src/session/session-manager.test.ts +0 -74
- package/src/session/session-manager.ts +0 -67
- package/src/session/session.test.ts +0 -126
- package/src/session/session.ts +0 -482
- package/src/test-fixtures/crash-adapter.js +0 -23
- package/src/test-fixtures/mock-adapter.js +0 -164
- package/src/test-fixtures/no-stop-adapter.js +0 -77
- package/src/test-fixtures/silent-adapter.js +0 -24
- package/src/test-fixtures/tcp-mock-adapter.js +0 -87
- package/tsconfig.json +0 -16
package/build/server.js
CHANGED
|
@@ -30,7 +30,8 @@ import { SessionManager } from "./session/session-manager.js";
|
|
|
30
30
|
import { DebugSession } from "./session/session.js";
|
|
31
31
|
import { runDebugInspect } from "./session/inspect.js";
|
|
32
32
|
import { runDebugRun } from "./session/run.js";
|
|
33
|
-
import {
|
|
33
|
+
import { inspectAdapterAvailability } from "./adapters/detect.js";
|
|
34
|
+
import { resolveProjectAdapter, runDebugDoctor } from "./diagnostics/doctor.js";
|
|
34
35
|
function readPackageVersion() {
|
|
35
36
|
try {
|
|
36
37
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -47,7 +48,7 @@ export function createServer() {
|
|
|
47
48
|
const registry = new AdapterRegistry(globalDir, projectDir);
|
|
48
49
|
const sessions = new SessionManager();
|
|
49
50
|
// Load persisted adapter configs (best-effort, don't block server start)
|
|
50
|
-
registry.load().catch((err) => {
|
|
51
|
+
const registryReady = registry.load().catch((err) => {
|
|
51
52
|
console.error("Warning: failed to load adapter configs:", err);
|
|
52
53
|
});
|
|
53
54
|
const server = new McpServer({
|
|
@@ -55,57 +56,227 @@ export function createServer() {
|
|
|
55
56
|
version: readPackageVersion(),
|
|
56
57
|
}, {
|
|
57
58
|
instructions: [
|
|
58
|
-
"
|
|
59
|
-
"",
|
|
60
|
-
"
|
|
61
|
-
"",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
" with `logMessage`) for printf-style debugging without modifying source.",
|
|
66
|
-
"- **`debug_launch` + step/evaluate** — Interactive multi-step session when you need to explore.",
|
|
67
|
-
"",
|
|
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.",
|
|
81
|
-
"",
|
|
82
|
-
"## Key Conventions",
|
|
83
|
-
"",
|
|
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.",
|
|
59
|
+
"Local source-code debugger using the Debug Adapter Protocol.",
|
|
60
|
+
"Use it when a test fails, a program crashes, runtime state is unclear, or code behavior cannot be explained statically.",
|
|
61
|
+
"Start with debug_doctor to detect the project and verify adapter readiness.",
|
|
62
|
+
"Prefer debug_at for a one-call breakpoint inspection: it selects the adapter, launches, waits, evaluates expressions, and cleans up.",
|
|
63
|
+
"Use debug_run when output or termination is enough. Use debug_launch only for interactive stepping or multiple inspection points.",
|
|
64
|
+
"For interactive sessions: debug_launch -> get_state/evaluate -> step -> debug_disconnect. A launch with breakpoints waits by default and reports stopped, terminated, or timeout explicitly.",
|
|
65
|
+
"Omit sessionId when exactly one session is active. Adapter-specific DAP fields belong in launchConfig. Tool errors use isError=true.",
|
|
92
66
|
].join("\n"),
|
|
93
67
|
});
|
|
68
|
+
const doctorOutputSchema = z.object({
|
|
69
|
+
cwd: z.string(),
|
|
70
|
+
ready: z.boolean(),
|
|
71
|
+
recommendedAdapter: z.string().optional(),
|
|
72
|
+
detected: z.object({
|
|
73
|
+
languages: z.array(z.string()),
|
|
74
|
+
testFrameworks: z.array(z.string()),
|
|
75
|
+
packageManager: z.string().optional(),
|
|
76
|
+
pythonPath: z.string().optional(),
|
|
77
|
+
launchJson: z.string().optional(),
|
|
78
|
+
}),
|
|
79
|
+
adapters: z.array(z.object({
|
|
80
|
+
type: z.string(),
|
|
81
|
+
source: z.enum(["builtin", "global", "project"]),
|
|
82
|
+
selected: z.boolean(),
|
|
83
|
+
available: z.boolean(),
|
|
84
|
+
command: z.string(),
|
|
85
|
+
reason: z.string().optional(),
|
|
86
|
+
installHint: z.string().optional(),
|
|
87
|
+
runtime: z.string().optional(),
|
|
88
|
+
program: z.string(),
|
|
89
|
+
transport: z.enum(["stdio", "tcp"]),
|
|
90
|
+
})),
|
|
91
|
+
nextSteps: z.array(z.string()),
|
|
92
|
+
});
|
|
93
|
+
const breakpointResultSchema = z.object({
|
|
94
|
+
id: z.number().optional(),
|
|
95
|
+
verified: z.boolean(),
|
|
96
|
+
line: z.number(),
|
|
97
|
+
message: z.string().optional(),
|
|
98
|
+
});
|
|
99
|
+
const debugAtOutputSchema = z.object({
|
|
100
|
+
adapter: z.string(),
|
|
101
|
+
hit: z.boolean(),
|
|
102
|
+
reason: z.string().optional(),
|
|
103
|
+
location: z
|
|
104
|
+
.object({
|
|
105
|
+
file: z.string(),
|
|
106
|
+
line: z.number(),
|
|
107
|
+
functionName: z.string().optional(),
|
|
108
|
+
column: z.number().optional(),
|
|
109
|
+
})
|
|
110
|
+
.optional(),
|
|
111
|
+
values: z
|
|
112
|
+
.record(z.string(), z.object({
|
|
113
|
+
result: z.string().optional(),
|
|
114
|
+
type: z.string().optional(),
|
|
115
|
+
error: z.string().optional(),
|
|
116
|
+
}))
|
|
117
|
+
.optional(),
|
|
118
|
+
output: z.array(z.string()).optional(),
|
|
119
|
+
status: z.string().optional(),
|
|
120
|
+
timedOut: z.boolean().optional(),
|
|
121
|
+
breakpoint: breakpointResultSchema.optional(),
|
|
122
|
+
});
|
|
123
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
124
|
+
// High-level LLM entry points
|
|
125
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
126
|
+
server.registerTool("debug_doctor", {
|
|
127
|
+
title: "Check debugger readiness",
|
|
128
|
+
description: "Inspect a local project, detect its language/tooling, select the likely debug adapter, verify the complete adapter command, and return concrete remediation. Use this before debugging or when an adapter fails to start.",
|
|
129
|
+
inputSchema: z.object({
|
|
130
|
+
cwd: z.string().optional().describe("Project directory (default: server working directory)"),
|
|
131
|
+
adapter: z.string().optional().describe("Adapter to validate instead of auto-detecting one"),
|
|
132
|
+
}),
|
|
133
|
+
outputSchema: doctorOutputSchema,
|
|
134
|
+
annotations: {
|
|
135
|
+
readOnlyHint: true,
|
|
136
|
+
destructiveHint: false,
|
|
137
|
+
idempotentHint: true,
|
|
138
|
+
openWorldHint: false,
|
|
139
|
+
},
|
|
140
|
+
}, async (options) => {
|
|
141
|
+
try {
|
|
142
|
+
await registryReady;
|
|
143
|
+
const result = await runDebugDoctor(registry, options);
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
146
|
+
structuredContent: { ...result },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
152
|
+
isError: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
server.registerTool("debug_at", {
|
|
157
|
+
title: "Inspect runtime state at a line",
|
|
158
|
+
description: "Primary one-shot debugging tool for bugs and failing behavior. Auto-selects a ready adapter, launches the program, stops at file:line, evaluates expressions, reports breakpoint/termination/timeout details, and cleans up.",
|
|
159
|
+
inputSchema: z.object({
|
|
160
|
+
cwd: z.string().optional().describe("Project and debuggee working directory"),
|
|
161
|
+
adapter: z.string().optional().describe("Adapter type; auto-detected from file/project when omitted"),
|
|
162
|
+
program: z
|
|
163
|
+
.string()
|
|
164
|
+
.optional()
|
|
165
|
+
.describe("Launch target; defaults to file for Python/Node and cwd for Go"),
|
|
166
|
+
args: z.array(z.string()).optional().describe("Program arguments"),
|
|
167
|
+
env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
|
|
168
|
+
file: z.string().describe("Source file containing the breakpoint"),
|
|
169
|
+
line: z.number().int().positive().describe("One-based breakpoint line"),
|
|
170
|
+
condition: z.string().optional().describe("Conditional breakpoint expression"),
|
|
171
|
+
hitCondition: z.string().optional().describe("Adapter-specific hit count condition"),
|
|
172
|
+
expressions: z.array(z.string()).optional().describe("Expressions to evaluate in the stopped frame"),
|
|
173
|
+
timeout: z.number().positive().optional().describe("Breakpoint wait timeout in milliseconds"),
|
|
174
|
+
launchConfig: z
|
|
175
|
+
.record(z.string(), z.unknown())
|
|
176
|
+
.optional()
|
|
177
|
+
.describe("Adapter-specific DAP launch fields"),
|
|
178
|
+
}),
|
|
179
|
+
outputSchema: debugAtOutputSchema,
|
|
180
|
+
annotations: {
|
|
181
|
+
readOnlyHint: false,
|
|
182
|
+
destructiveHint: true,
|
|
183
|
+
idempotentHint: false,
|
|
184
|
+
openWorldHint: false,
|
|
185
|
+
},
|
|
186
|
+
}, async (options) => {
|
|
187
|
+
try {
|
|
188
|
+
await registryReady;
|
|
189
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
190
|
+
const sourceFile = path.resolve(cwd, options.file);
|
|
191
|
+
const resolved = await resolveProjectAdapter(registry, {
|
|
192
|
+
cwd,
|
|
193
|
+
adapter: options.adapter,
|
|
194
|
+
sourceFile,
|
|
195
|
+
});
|
|
196
|
+
const adapter = resolved.result.recommendedAdapter;
|
|
197
|
+
if (!adapter || !resolved.config || !resolved.result.ready) {
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: `Debugger is not ready:\n${JSON.stringify(resolved.result, null, 2)}`,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
isError: true,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const hasLaunchTarget = options.launchConfig !== undefined &&
|
|
209
|
+
["program", "module", "code"].some((key) => options.launchConfig?.[key] !== undefined);
|
|
210
|
+
if (!options.program && !hasLaunchTarget && adapter === "cppdbg") {
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: "Error: C/C++ debugging requires the compiled executable in program or launchConfig.program.",
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
isError: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const program = options.program ??
|
|
222
|
+
(hasLaunchTarget ? undefined : adapter === "go" ? cwd : sourceFile);
|
|
223
|
+
const result = await runDebugInspect(registry, sessions, {
|
|
224
|
+
adapter,
|
|
225
|
+
adapterConfig: resolved.config,
|
|
226
|
+
program,
|
|
227
|
+
args: options.args,
|
|
228
|
+
cwd,
|
|
229
|
+
env: options.env,
|
|
230
|
+
at: {
|
|
231
|
+
file: sourceFile,
|
|
232
|
+
line: options.line,
|
|
233
|
+
condition: options.condition,
|
|
234
|
+
hitCondition: options.hitCondition,
|
|
235
|
+
},
|
|
236
|
+
expressions: options.expressions,
|
|
237
|
+
timeout: options.timeout,
|
|
238
|
+
launchConfig: options.launchConfig,
|
|
239
|
+
});
|
|
240
|
+
const structuredContent = { adapter, ...result };
|
|
241
|
+
return {
|
|
242
|
+
content: [
|
|
243
|
+
{ type: "text", text: JSON.stringify(structuredContent, null, 2) },
|
|
244
|
+
],
|
|
245
|
+
structuredContent,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
251
|
+
isError: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
});
|
|
94
255
|
// ────────────────────────────────────────────────────────────────────────────
|
|
95
256
|
// 1. list_adapters
|
|
96
257
|
// ────────────────────────────────────────────────────────────────────────────
|
|
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.", {},
|
|
258
|
+
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.", {}, {
|
|
259
|
+
readOnlyHint: true,
|
|
260
|
+
destructiveHint: false,
|
|
261
|
+
idempotentHint: true,
|
|
262
|
+
openWorldHint: false,
|
|
263
|
+
}, async () => {
|
|
98
264
|
try {
|
|
265
|
+
await registryReady;
|
|
99
266
|
const adapters = registry.listAll();
|
|
100
267
|
const results = await Promise.all(adapters.map(async (a) => {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
return { ...a, available };
|
|
268
|
+
const availability = await inspectAdapterAvailability(a);
|
|
269
|
+
return { ...a, ...availability };
|
|
104
270
|
}));
|
|
105
271
|
const available = results.filter((r) => r.available);
|
|
106
272
|
const missing = results
|
|
107
273
|
.filter((r) => !r.available)
|
|
108
|
-
.map((r) => ({
|
|
274
|
+
.map((r) => ({
|
|
275
|
+
type: r.type,
|
|
276
|
+
command: r.command,
|
|
277
|
+
reason: r.reason,
|
|
278
|
+
installHint: r.installHint,
|
|
279
|
+
}));
|
|
109
280
|
const summary = missing.length === 0
|
|
110
281
|
? `${available.length}/${results.length} adapters available`
|
|
111
282
|
: `${available.length}/${results.length} adapters available; missing: ${missing.map((m) => m.type).join(", ")}`;
|
|
@@ -135,10 +306,27 @@ export function createServer() {
|
|
|
135
306
|
args: z.array(z.string()).optional().describe("Additional arguments to pass to the adapter"),
|
|
136
307
|
launchDefaults: z.record(z.string(), z.unknown()).optional().describe("Default launch configuration properties"),
|
|
137
308
|
installHint: z.string().optional().describe("Hint shown when adapter is not installed"),
|
|
309
|
+
transport: z.enum(["stdio", "tcp"]).optional().describe("DAP transport used by the adapter"),
|
|
310
|
+
portMatcher: z.string().optional().describe("Regex whose first capture group is the TCP port"),
|
|
138
311
|
scope: z.enum(["global", "project"]).optional().describe("Where to persist the adapter config (default: project)"),
|
|
139
|
-
},
|
|
312
|
+
}, {
|
|
313
|
+
readOnlyHint: false,
|
|
314
|
+
destructiveHint: true,
|
|
315
|
+
idempotentHint: true,
|
|
316
|
+
openWorldHint: false,
|
|
317
|
+
}, async ({ type, program, runtime, args, launchDefaults, installHint, transport, portMatcher, scope }) => {
|
|
140
318
|
try {
|
|
141
|
-
|
|
319
|
+
await registryReady;
|
|
320
|
+
const config = {
|
|
321
|
+
type,
|
|
322
|
+
program,
|
|
323
|
+
runtime,
|
|
324
|
+
args,
|
|
325
|
+
launchDefaults,
|
|
326
|
+
installHint,
|
|
327
|
+
transport,
|
|
328
|
+
portMatcher,
|
|
329
|
+
};
|
|
142
330
|
const saveScope = scope ?? "project";
|
|
143
331
|
registry.register(config, saveScope);
|
|
144
332
|
await registry.save(saveScope);
|
|
@@ -161,12 +349,16 @@ export function createServer() {
|
|
|
161
349
|
// ────────────────────────────────────────────────────────────────────────────
|
|
162
350
|
// 3. debug_launch
|
|
163
351
|
// ────────────────────────────────────────────────────────────────────────────
|
|
164
|
-
server.tool("debug_launch", "Launch
|
|
352
|
+
server.tool("debug_launch", "Launch an interactive debug session. With a breakpoint or stopOnEntry, waits by default and reports whether execution stopped, terminated, or timed out.", {
|
|
165
353
|
adapter: z.string().describe("Adapter type to use (e.g. 'python', 'node')"),
|
|
166
|
-
program: z.string().describe("Path to the program to debug"),
|
|
354
|
+
program: z.string().optional().describe("Path to the program to debug; may instead be supplied through launchConfig"),
|
|
167
355
|
args: z.array(z.string()).optional().describe("Program arguments"),
|
|
168
356
|
cwd: z.string().optional().describe("Working directory for the program"),
|
|
169
357
|
env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
|
|
358
|
+
launchConfig: z
|
|
359
|
+
.record(z.string(), z.unknown())
|
|
360
|
+
.optional()
|
|
361
|
+
.describe("Adapter-specific DAP launch properties; explicit common fields take precedence"),
|
|
170
362
|
stopOnEntry: z
|
|
171
363
|
.boolean()
|
|
172
364
|
.optional()
|
|
@@ -185,6 +377,11 @@ export function createServer() {
|
|
|
185
377
|
.array(z.string())
|
|
186
378
|
.optional()
|
|
187
379
|
.describe("Exception filter IDs (e.g. ['uncaughtExceptions'])"),
|
|
380
|
+
waitForStop: z
|
|
381
|
+
.boolean()
|
|
382
|
+
.optional()
|
|
383
|
+
.describe("Wait for stopped/terminated outcome; defaults to true when a stop is expected"),
|
|
384
|
+
timeout: z.number().positive().optional().describe("Wait timeout in milliseconds (default: 30000)"),
|
|
188
385
|
adapterConfig: z
|
|
189
386
|
.object({
|
|
190
387
|
type: z.string(),
|
|
@@ -198,11 +395,14 @@ export function createServer() {
|
|
|
198
395
|
})
|
|
199
396
|
.optional()
|
|
200
397
|
.describe("Override adapter config inline instead of looking up from registry"),
|
|
201
|
-
}, async ({ adapter, program, args, cwd, env, stopOnEntry, breakpoints, exceptionBreakpoints, adapterConfig, }) => {
|
|
202
|
-
// Default:
|
|
203
|
-
const
|
|
398
|
+
}, async ({ adapter, program, args, cwd, env, launchConfig, stopOnEntry, breakpoints, exceptionBreakpoints, waitForStop, timeout, adapterConfig, }) => {
|
|
399
|
+
// Default: run to a configured breakpoint/exception; otherwise stop at entry.
|
|
400
|
+
const expectsConfiguredStop = (breakpoints !== undefined && breakpoints.length > 0) ||
|
|
401
|
+
(exceptionBreakpoints !== undefined && exceptionBreakpoints.length > 0);
|
|
402
|
+
const shouldStop = stopOnEntry ?? !expectsConfiguredStop;
|
|
204
403
|
let session;
|
|
205
404
|
try {
|
|
405
|
+
await registryReady;
|
|
206
406
|
const config = adapterConfig ??
|
|
207
407
|
registry.get(adapter);
|
|
208
408
|
if (!config) {
|
|
@@ -224,9 +424,11 @@ export function createServer() {
|
|
|
224
424
|
// Build launch request
|
|
225
425
|
const launchArgs = {
|
|
226
426
|
...config.launchDefaults,
|
|
227
|
-
|
|
427
|
+
...launchConfig,
|
|
228
428
|
stopOnEntry: shouldStop,
|
|
229
429
|
};
|
|
430
|
+
if (program !== undefined)
|
|
431
|
+
launchArgs.program = program;
|
|
230
432
|
if (args !== undefined)
|
|
231
433
|
launchArgs.args = args;
|
|
232
434
|
if (cwd !== undefined)
|
|
@@ -253,6 +455,7 @@ export function createServer() {
|
|
|
253
455
|
// launch — that's why we fire launch first and wait for initialized here.
|
|
254
456
|
await session.waitForInitialized(10000);
|
|
255
457
|
// Set breakpoints grouped by file
|
|
458
|
+
const breakpointResults = [];
|
|
256
459
|
if (breakpoints && breakpoints.length > 0) {
|
|
257
460
|
const byFile = new Map();
|
|
258
461
|
for (const bp of breakpoints) {
|
|
@@ -261,12 +464,13 @@ export function createServer() {
|
|
|
261
464
|
byFile.set(bp.file, existing);
|
|
262
465
|
}
|
|
263
466
|
for (const [file, bps] of byFile) {
|
|
264
|
-
await session.setBreakpoints(file, bps.map((b) => ({
|
|
467
|
+
const results = await session.setBreakpoints(file, bps.map((b) => ({
|
|
265
468
|
line: b.line,
|
|
266
469
|
condition: b.condition,
|
|
267
470
|
hitCondition: b.hitCondition,
|
|
268
471
|
logMessage: b.logMessage,
|
|
269
472
|
})));
|
|
473
|
+
breakpointResults.push({ file, breakpoints: results });
|
|
270
474
|
}
|
|
271
475
|
}
|
|
272
476
|
// Set exception breakpoints
|
|
@@ -277,14 +481,23 @@ export function createServer() {
|
|
|
277
481
|
await session.configurationDone();
|
|
278
482
|
// Now await the launch response
|
|
279
483
|
await launchPromise;
|
|
484
|
+
const shouldWait = waitForStop ?? (shouldStop || expectsConfiguredStop);
|
|
280
485
|
let stateData;
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
486
|
+
if (shouldWait) {
|
|
487
|
+
const startedAt = Date.now();
|
|
488
|
+
const outcome = await session.waitForStopOrTermination(timeout ?? 30000);
|
|
489
|
+
if (outcome.status === "stopped") {
|
|
284
490
|
stateData = (await session.collectState());
|
|
285
491
|
}
|
|
286
|
-
|
|
287
|
-
|
|
492
|
+
else if (outcome.status === "terminated") {
|
|
493
|
+
const { output } = session.getOutput();
|
|
494
|
+
stateData = {
|
|
495
|
+
status: "terminated",
|
|
496
|
+
output: output.map((entry) => entry.text),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
stateData = { status: session.state, timedOut: true, elapsedMs: Date.now() - startedAt };
|
|
288
501
|
}
|
|
289
502
|
}
|
|
290
503
|
else {
|
|
@@ -294,7 +507,7 @@ export function createServer() {
|
|
|
294
507
|
content: [
|
|
295
508
|
{
|
|
296
509
|
type: "text",
|
|
297
|
-
text: JSON.stringify({ sessionId: id, ...stateData }, null, 2),
|
|
510
|
+
text: JSON.stringify({ sessionId: id, breakpoints: breakpointResults, ...stateData }, null, 2),
|
|
298
511
|
},
|
|
299
512
|
],
|
|
300
513
|
};
|
|
@@ -335,6 +548,7 @@ export function createServer() {
|
|
|
335
548
|
}, async ({ adapter, port, host, processId, attachConfig, adapterConfig }) => {
|
|
336
549
|
let session;
|
|
337
550
|
try {
|
|
551
|
+
await registryReady;
|
|
338
552
|
const config = adapterConfig ?? registry.get(adapter);
|
|
339
553
|
if (!config) {
|
|
340
554
|
return {
|
|
@@ -459,6 +673,11 @@ export function createServer() {
|
|
|
459
673
|
.number()
|
|
460
674
|
.optional()
|
|
461
675
|
.describe("If running, wait up to this many milliseconds for the program to stop (default: 0 = don't wait)"),
|
|
676
|
+
}, {
|
|
677
|
+
readOnlyHint: true,
|
|
678
|
+
destructiveHint: false,
|
|
679
|
+
idempotentHint: true,
|
|
680
|
+
openWorldHint: false,
|
|
462
681
|
}, async ({ sessionId, threadId, depth, variableDepth, timeout }) => {
|
|
463
682
|
try {
|
|
464
683
|
const session = sessions.resolve(sessionId);
|
|
@@ -470,16 +689,13 @@ export function createServer() {
|
|
|
470
689
|
};
|
|
471
690
|
}
|
|
472
691
|
if (waitMs > 0 && session.state === "running") {
|
|
473
|
-
|
|
474
|
-
|
|
692
|
+
const outcome = await session.waitForStopOrTermination(waitMs);
|
|
693
|
+
if (outcome.status === "stopped") {
|
|
475
694
|
const state = await session.collectState(threadId, depth ?? 10, variableDepth ?? 1);
|
|
476
695
|
return {
|
|
477
696
|
content: [{ type: "text", text: JSON.stringify(state, null, 2) }],
|
|
478
697
|
};
|
|
479
698
|
}
|
|
480
|
-
catch {
|
|
481
|
-
// Timed out — fall through to return running status
|
|
482
|
-
}
|
|
483
699
|
}
|
|
484
700
|
const { output } = session.getOutput();
|
|
485
701
|
return {
|
|
@@ -580,13 +796,10 @@ export function createServer() {
|
|
|
580
796
|
await session.pause(threadId);
|
|
581
797
|
break;
|
|
582
798
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
catch (err) {
|
|
587
|
-
// Timeout or terminated
|
|
799
|
+
const outcome = await session.waitForStopOrTermination(waitMs);
|
|
800
|
+
if (outcome.status !== "stopped") {
|
|
588
801
|
const elapsed = Date.now() - startTime;
|
|
589
|
-
if (
|
|
802
|
+
if (outcome.status === "terminated") {
|
|
590
803
|
const { output } = session.getOutput();
|
|
591
804
|
return {
|
|
592
805
|
content: [
|
|
@@ -606,17 +819,6 @@ export function createServer() {
|
|
|
606
819
|
],
|
|
607
820
|
};
|
|
608
821
|
}
|
|
609
|
-
if (session.state === "terminated") {
|
|
610
|
-
const { output } = session.getOutput();
|
|
611
|
-
return {
|
|
612
|
-
content: [
|
|
613
|
-
{
|
|
614
|
-
type: "text",
|
|
615
|
-
text: JSON.stringify({ status: "terminated", output: output.map((e) => e.text) }, null, 2),
|
|
616
|
-
},
|
|
617
|
-
],
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
822
|
const state = await session.collectState(threadId);
|
|
621
823
|
return {
|
|
622
824
|
content: [{ type: "text", text: JSON.stringify(state, null, 2) }],
|
|
@@ -643,6 +845,11 @@ export function createServer() {
|
|
|
643
845
|
.number()
|
|
644
846
|
.optional()
|
|
645
847
|
.describe("Return only entries with seq > since. Pass the `nextSince` value from a prior call to fetch incremental output."),
|
|
848
|
+
}, {
|
|
849
|
+
readOnlyHint: false,
|
|
850
|
+
destructiveHint: true,
|
|
851
|
+
idempotentHint: false,
|
|
852
|
+
openWorldHint: false,
|
|
646
853
|
}, async ({ sessionId, category, clear, since }) => {
|
|
647
854
|
try {
|
|
648
855
|
const session = sessions.resolve(sessionId);
|
|
@@ -746,14 +953,18 @@ export function createServer() {
|
|
|
746
953
|
await newSession.configurationDone();
|
|
747
954
|
await launchPromise;
|
|
748
955
|
const stopOnEntry = launchConfig.stopOnEntry !== false;
|
|
956
|
+
const shouldWait = stopOnEntry || savedBreakpoints.size > 0;
|
|
749
957
|
let stateData;
|
|
750
|
-
if (
|
|
751
|
-
|
|
752
|
-
|
|
958
|
+
if (shouldWait) {
|
|
959
|
+
const outcome = await newSession.waitForStopOrTermination(30000);
|
|
960
|
+
if (outcome.status === "stopped") {
|
|
753
961
|
stateData = (await newSession.collectState());
|
|
754
962
|
}
|
|
755
|
-
|
|
756
|
-
stateData = {
|
|
963
|
+
else {
|
|
964
|
+
stateData = {
|
|
965
|
+
status: outcome.status === "timeout" ? newSession.state : "terminated",
|
|
966
|
+
timedOut: outcome.status === "timeout",
|
|
967
|
+
};
|
|
757
968
|
}
|
|
758
969
|
}
|
|
759
970
|
else {
|
|
@@ -804,6 +1015,10 @@ export function createServer() {
|
|
|
804
1015
|
.number()
|
|
805
1016
|
.optional()
|
|
806
1017
|
.describe("Milliseconds to wait for the breakpoint to be hit (default: 30000)"),
|
|
1018
|
+
launchConfig: z
|
|
1019
|
+
.record(z.string(), z.unknown())
|
|
1020
|
+
.optional()
|
|
1021
|
+
.describe("Adapter-specific DAP launch fields"),
|
|
807
1022
|
adapterConfig: z
|
|
808
1023
|
.object({
|
|
809
1024
|
type: z.string(),
|
|
@@ -819,6 +1034,7 @@ export function createServer() {
|
|
|
819
1034
|
.describe("Override adapter config inline"),
|
|
820
1035
|
}, async (opts) => {
|
|
821
1036
|
try {
|
|
1037
|
+
await registryReady;
|
|
822
1038
|
const result = await runDebugInspect(registry, sessions, opts);
|
|
823
1039
|
return {
|
|
824
1040
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
@@ -836,6 +1052,11 @@ export function createServer() {
|
|
|
836
1052
|
// ────────────────────────────────────────────────────────────────────────────
|
|
837
1053
|
server.tool("list_breakpoints", "List the breakpoints currently registered with a session, grouped by source file. Includes regular breakpoints, conditional breakpoints, and logpoints.", {
|
|
838
1054
|
sessionId: z.string().optional().describe("Session ID (omit if only one session is active)"),
|
|
1055
|
+
}, {
|
|
1056
|
+
readOnlyHint: true,
|
|
1057
|
+
destructiveHint: false,
|
|
1058
|
+
idempotentHint: true,
|
|
1059
|
+
openWorldHint: false,
|
|
839
1060
|
}, async ({ sessionId }) => {
|
|
840
1061
|
try {
|
|
841
1062
|
const session = sessions.resolve(sessionId);
|
|
@@ -897,6 +1118,7 @@ export function createServer() {
|
|
|
897
1118
|
.describe("Override adapter config inline"),
|
|
898
1119
|
}, async (opts) => {
|
|
899
1120
|
try {
|
|
1121
|
+
await registryReady;
|
|
900
1122
|
const result = await runDebugRun(registry, sessions, opts);
|
|
901
1123
|
return {
|
|
902
1124
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|