dap-mcp-server 0.1.12 → 0.1.17
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 +50 -10
- package/SKILL.md +39 -184
- package/build/adapters/builtin.js +25 -0
- package/build/adapters/builtin.js.map +1 -1
- 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/recipes.d.ts +46 -0
- package/build/adapters/recipes.js +128 -0
- package/build/adapters/recipes.js.map +1 -0
- package/build/adapters/registry.js +35 -12
- package/build/adapters/registry.js.map +1 -1
- package/build/dap/types.d.ts +20 -0
- package/build/diagnostics/doctor.d.ts +70 -0
- package/build/diagnostics/doctor.js +268 -0
- package/build/diagnostics/doctor.js.map +1 -0
- package/build/server.js +432 -161
- package/build/server.js.map +1 -1
- package/build/session/inspect.d.ts +12 -2
- package/build/session/inspect.js +33 -20
- package/build/session/inspect.js.map +1 -1
- package/build/session/run.d.ts +2 -0
- package/build/session/run.js +13 -23
- package/build/session/run.js.map +1 -1
- package/build/session/session.d.ts +38 -2
- package/build/session/session.js +158 -12
- package/build/session/session.js.map +1 -1
- package/{src/test-fixtures/crash-adapter.js → build/session/state-format.d.ts} +11 -2
- package/build/session/state-format.js +62 -0
- package/build/session/state-format.js.map +1 -0
- 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 +51 -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/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,10 @@ 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 { conciseDoctorResult, resolveProjectAdapter, runDebugDoctor, } from "./diagnostics/doctor.js";
|
|
35
|
+
import { buildLaunchArgs, defaultCargoProgram } from "./adapters/recipes.js";
|
|
36
|
+
import { COMPACT_STACK_DEPTH, renderState } from "./session/state-format.js";
|
|
34
37
|
function readPackageVersion() {
|
|
35
38
|
try {
|
|
36
39
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -47,7 +50,7 @@ export function createServer() {
|
|
|
47
50
|
const registry = new AdapterRegistry(globalDir, projectDir);
|
|
48
51
|
const sessions = new SessionManager();
|
|
49
52
|
// Load persisted adapter configs (best-effort, don't block server start)
|
|
50
|
-
registry.load().catch((err) => {
|
|
53
|
+
const registryReady = registry.load().catch((err) => {
|
|
51
54
|
console.error("Warning: failed to load adapter configs:", err);
|
|
52
55
|
});
|
|
53
56
|
const server = new McpServer({
|
|
@@ -55,57 +58,303 @@ export function createServer() {
|
|
|
55
58
|
version: readPackageVersion(),
|
|
56
59
|
}, {
|
|
57
60
|
instructions: [
|
|
58
|
-
"
|
|
59
|
-
"",
|
|
60
|
-
"
|
|
61
|
-
"",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
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.",
|
|
61
|
+
"Local source-code debugger using the Debug Adapter Protocol.",
|
|
62
|
+
"Use it when a test fails, a program crashes, runtime state is unclear, or code behavior cannot be explained statically.",
|
|
63
|
+
"Start with debug_doctor to detect the project and verify adapter readiness.",
|
|
64
|
+
"Prefer debug_at for a one-call breakpoint inspection: it selects the adapter, launches, waits, evaluates expressions, and cleans up.",
|
|
65
|
+
"Use debug_run when output or termination is enough. Use debug_launch only for interactive stepping or multiple inspection points.",
|
|
66
|
+
"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.",
|
|
67
|
+
"step answers compactly (frame-local scopes, short stack) and states carry only output not reported before; use get_state for the full picture and get_output for the whole buffer.",
|
|
68
|
+
"To debug an already-running or suspended process, pass breakpoints to debug_attach so they bind before it resumes.",
|
|
69
|
+
"Omit sessionId when exactly one session is active. Adapter-specific DAP fields belong in launchConfig. Tool errors use isError=true.",
|
|
92
70
|
].join("\n"),
|
|
93
71
|
});
|
|
72
|
+
const doctorOutputSchema = z.object({
|
|
73
|
+
cwd: z.string(),
|
|
74
|
+
ready: z.boolean(),
|
|
75
|
+
recommendedAdapter: z.string().optional(),
|
|
76
|
+
detected: z.object({
|
|
77
|
+
languages: z.array(z.string()),
|
|
78
|
+
testFrameworks: z.array(z.string()),
|
|
79
|
+
packageManager: z.string().optional(),
|
|
80
|
+
pythonPath: z.string().optional(),
|
|
81
|
+
launchJson: z.string().optional(),
|
|
82
|
+
}),
|
|
83
|
+
adapters: z.array(z.object({
|
|
84
|
+
type: z.string(),
|
|
85
|
+
source: z.enum(["builtin", "global", "project"]),
|
|
86
|
+
selected: z.boolean(),
|
|
87
|
+
available: z.boolean(),
|
|
88
|
+
command: z.string(),
|
|
89
|
+
reason: z.string().optional(),
|
|
90
|
+
installHint: z.string().optional(),
|
|
91
|
+
runtime: z.string().optional(),
|
|
92
|
+
program: z.string(),
|
|
93
|
+
transport: z.enum(["stdio", "tcp"]),
|
|
94
|
+
})),
|
|
95
|
+
otherAdapters: z.array(z.string()).optional(),
|
|
96
|
+
nextSteps: z.array(z.string()),
|
|
97
|
+
});
|
|
98
|
+
const breakpointResultSchema = z.object({
|
|
99
|
+
id: z.number().optional(),
|
|
100
|
+
verified: z.boolean(),
|
|
101
|
+
line: z.number(),
|
|
102
|
+
message: z.string().optional(),
|
|
103
|
+
});
|
|
104
|
+
const debugAtOutputSchema = z.object({
|
|
105
|
+
adapter: z.string(),
|
|
106
|
+
hit: z.boolean(),
|
|
107
|
+
reason: z.string().optional(),
|
|
108
|
+
hint: z.string().optional(),
|
|
109
|
+
exception: z
|
|
110
|
+
.object({
|
|
111
|
+
id: z.string().optional(),
|
|
112
|
+
description: z.string().optional(),
|
|
113
|
+
breakMode: z.string().optional(),
|
|
114
|
+
})
|
|
115
|
+
.optional(),
|
|
116
|
+
location: z
|
|
117
|
+
.object({
|
|
118
|
+
file: z.string(),
|
|
119
|
+
line: z.number(),
|
|
120
|
+
functionName: z.string().optional(),
|
|
121
|
+
column: z.number().optional(),
|
|
122
|
+
})
|
|
123
|
+
.optional(),
|
|
124
|
+
values: z
|
|
125
|
+
.record(z.string(), z.object({
|
|
126
|
+
result: z.string().optional(),
|
|
127
|
+
type: z.string().optional(),
|
|
128
|
+
error: z.string().optional(),
|
|
129
|
+
}))
|
|
130
|
+
.optional(),
|
|
131
|
+
output: z.array(z.string()).optional(),
|
|
132
|
+
status: z.string().optional(),
|
|
133
|
+
exitCode: z.number().optional(),
|
|
134
|
+
timedOut: z.boolean().optional(),
|
|
135
|
+
breakpoint: breakpointResultSchema.optional(),
|
|
136
|
+
});
|
|
137
|
+
const breakpointInputSchema = z.object({
|
|
138
|
+
file: z.string().describe("Source file path"),
|
|
139
|
+
line: z.number().describe("Line number"),
|
|
140
|
+
condition: z.string().optional(),
|
|
141
|
+
hitCondition: z.string().optional(),
|
|
142
|
+
logMessage: z.string().optional(),
|
|
143
|
+
});
|
|
144
|
+
const detailSchema = z.enum(["compact", "full"]);
|
|
145
|
+
/** Set breakpoints grouped by file; must run between `initialized` and configurationDone. */
|
|
146
|
+
async function applyBreakpoints(session, breakpoints = []) {
|
|
147
|
+
const byFile = new Map();
|
|
148
|
+
for (const bp of breakpoints) {
|
|
149
|
+
const existing = byFile.get(bp.file) ?? [];
|
|
150
|
+
existing.push(bp);
|
|
151
|
+
byFile.set(bp.file, existing);
|
|
152
|
+
}
|
|
153
|
+
const results = [];
|
|
154
|
+
for (const [file, bps] of byFile) {
|
|
155
|
+
const fileResults = await session.setBreakpoints(file, bps.map(({ line, condition, hitCondition, logMessage }) => ({
|
|
156
|
+
line,
|
|
157
|
+
condition,
|
|
158
|
+
hitCondition,
|
|
159
|
+
logMessage,
|
|
160
|
+
})));
|
|
161
|
+
results.push({ file, breakpoints: fileResults });
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
/** What to report once the program is no longer running under our control. */
|
|
166
|
+
async function terminatedReport(session) {
|
|
167
|
+
return {
|
|
168
|
+
status: "terminated",
|
|
169
|
+
exitCode: await session.waitForExitCode(),
|
|
170
|
+
...session.unreportedOutput(true),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Wait for the first stop after launch/attach/restart and describe the outcome. */
|
|
174
|
+
async function awaitFirstStop(session, timeout) {
|
|
175
|
+
const startedAt = Date.now();
|
|
176
|
+
const outcome = await session.waitForStopOrTermination(timeout);
|
|
177
|
+
if (outcome.status === "stopped") {
|
|
178
|
+
const state = await session.collectState(undefined, 10, 1, { consumeOutput: true });
|
|
179
|
+
return state;
|
|
180
|
+
}
|
|
181
|
+
if (outcome.status === "terminated")
|
|
182
|
+
return terminatedReport(session);
|
|
183
|
+
return { status: session.state, timedOut: true, elapsedMs: Date.now() - startedAt };
|
|
184
|
+
}
|
|
185
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
186
|
+
// High-level LLM entry points
|
|
187
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
188
|
+
server.registerTool("debug_doctor", {
|
|
189
|
+
title: "Check debugger readiness",
|
|
190
|
+
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.",
|
|
191
|
+
inputSchema: z.object({
|
|
192
|
+
cwd: z.string().optional().describe("Project directory (default: server working directory)"),
|
|
193
|
+
adapter: z.string().optional().describe("Adapter to validate instead of auto-detecting one"),
|
|
194
|
+
verbose: z
|
|
195
|
+
.boolean()
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("Include full details for every known adapter, not just the selected one"),
|
|
198
|
+
}),
|
|
199
|
+
outputSchema: doctorOutputSchema,
|
|
200
|
+
annotations: {
|
|
201
|
+
readOnlyHint: true,
|
|
202
|
+
destructiveHint: false,
|
|
203
|
+
idempotentHint: true,
|
|
204
|
+
openWorldHint: false,
|
|
205
|
+
},
|
|
206
|
+
}, async (options) => {
|
|
207
|
+
try {
|
|
208
|
+
await registryReady;
|
|
209
|
+
const result = await runDebugDoctor(registry, options);
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
212
|
+
structuredContent: { ...result },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
218
|
+
isError: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
server.registerTool("debug_at", {
|
|
223
|
+
title: "Inspect runtime state at a line",
|
|
224
|
+
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.",
|
|
225
|
+
inputSchema: z.object({
|
|
226
|
+
cwd: z.string().optional().describe("Project and debuggee working directory"),
|
|
227
|
+
adapter: z.string().optional().describe("Adapter type; auto-detected from file/project when omitted"),
|
|
228
|
+
program: z
|
|
229
|
+
.string()
|
|
230
|
+
.optional()
|
|
231
|
+
.describe("Launch target; defaults to file for Python/Node and cwd for Go"),
|
|
232
|
+
args: z.array(z.string()).optional().describe("Program arguments"),
|
|
233
|
+
env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
|
|
234
|
+
file: z.string().describe("Source file containing the breakpoint"),
|
|
235
|
+
line: z.number().int().positive().describe("One-based breakpoint line"),
|
|
236
|
+
condition: z.string().optional().describe("Conditional breakpoint expression"),
|
|
237
|
+
hitCondition: z.string().optional().describe("Adapter-specific hit count condition"),
|
|
238
|
+
expressions: z.array(z.string()).optional().describe("Expressions to evaluate in the stopped frame"),
|
|
239
|
+
timeout: z.number().positive().optional().describe("Breakpoint wait timeout in milliseconds"),
|
|
240
|
+
launchConfig: z
|
|
241
|
+
.record(z.string(), z.unknown())
|
|
242
|
+
.optional()
|
|
243
|
+
.describe("Adapter-specific DAP launch fields"),
|
|
244
|
+
}),
|
|
245
|
+
outputSchema: debugAtOutputSchema,
|
|
246
|
+
annotations: {
|
|
247
|
+
readOnlyHint: false,
|
|
248
|
+
destructiveHint: true,
|
|
249
|
+
idempotentHint: false,
|
|
250
|
+
openWorldHint: false,
|
|
251
|
+
},
|
|
252
|
+
}, async (options) => {
|
|
253
|
+
try {
|
|
254
|
+
await registryReady;
|
|
255
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
256
|
+
const sourceFile = path.resolve(cwd, options.file);
|
|
257
|
+
const resolved = await resolveProjectAdapter(registry, {
|
|
258
|
+
cwd,
|
|
259
|
+
adapter: options.adapter,
|
|
260
|
+
sourceFile,
|
|
261
|
+
});
|
|
262
|
+
const adapter = resolved.result.recommendedAdapter;
|
|
263
|
+
if (!adapter || !resolved.config || !resolved.result.ready) {
|
|
264
|
+
const { recommendedAdapter, detected, adapters, otherAdapters, nextSteps } = conciseDoctorResult(resolved.result);
|
|
265
|
+
const summary = {
|
|
266
|
+
recommendedAdapter,
|
|
267
|
+
languages: detected.languages,
|
|
268
|
+
reason: adapters[0]?.reason,
|
|
269
|
+
nextSteps,
|
|
270
|
+
otherAdapters,
|
|
271
|
+
};
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
text: `Debugger is not ready: ${JSON.stringify(summary)}\nRun debug_doctor with verbose=true for full adapter details.`,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
isError: true,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const hasLaunchTarget = options.launchConfig !== undefined &&
|
|
283
|
+
["program", "module", "code"].some((key) => options.launchConfig?.[key] !== undefined);
|
|
284
|
+
const compiledProgram = !options.program && !hasLaunchTarget && adapter === "cppdbg"
|
|
285
|
+
? await defaultCargoProgram(cwd)
|
|
286
|
+
: undefined;
|
|
287
|
+
if (!options.program && !hasLaunchTarget && adapter === "cppdbg" && !compiledProgram) {
|
|
288
|
+
return {
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: "Error: C/C++/Rust debugging requires the compiled executable in program or launchConfig.program. For a Cargo project, run `cargo build` first and program defaults to target/debug/<package name>.",
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
isError: true,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const program = options.program ??
|
|
299
|
+
compiledProgram ??
|
|
300
|
+
(hasLaunchTarget ? undefined : adapter === "go" ? cwd : sourceFile);
|
|
301
|
+
const result = await runDebugInspect(registry, sessions, {
|
|
302
|
+
adapter,
|
|
303
|
+
adapterConfig: resolved.config,
|
|
304
|
+
program,
|
|
305
|
+
args: options.args,
|
|
306
|
+
cwd,
|
|
307
|
+
env: options.env,
|
|
308
|
+
at: {
|
|
309
|
+
file: sourceFile,
|
|
310
|
+
line: options.line,
|
|
311
|
+
condition: options.condition,
|
|
312
|
+
hitCondition: options.hitCondition,
|
|
313
|
+
},
|
|
314
|
+
expressions: options.expressions,
|
|
315
|
+
timeout: options.timeout,
|
|
316
|
+
launchConfig: options.launchConfig,
|
|
317
|
+
});
|
|
318
|
+
const structuredContent = { adapter, ...result };
|
|
319
|
+
return {
|
|
320
|
+
content: [
|
|
321
|
+
{ type: "text", text: JSON.stringify(structuredContent, null, 2) },
|
|
322
|
+
],
|
|
323
|
+
structuredContent,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
return {
|
|
328
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
329
|
+
isError: true,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
});
|
|
94
333
|
// ────────────────────────────────────────────────────────────────────────────
|
|
95
334
|
// 1. list_adapters
|
|
96
335
|
// ────────────────────────────────────────────────────────────────────────────
|
|
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.", {},
|
|
336
|
+
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.", {}, {
|
|
337
|
+
readOnlyHint: true,
|
|
338
|
+
destructiveHint: false,
|
|
339
|
+
idempotentHint: true,
|
|
340
|
+
openWorldHint: false,
|
|
341
|
+
}, async () => {
|
|
98
342
|
try {
|
|
343
|
+
await registryReady;
|
|
99
344
|
const adapters = registry.listAll();
|
|
100
345
|
const results = await Promise.all(adapters.map(async (a) => {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
return { ...a, available };
|
|
346
|
+
const availability = await inspectAdapterAvailability(a);
|
|
347
|
+
return { ...a, ...availability };
|
|
104
348
|
}));
|
|
105
349
|
const available = results.filter((r) => r.available);
|
|
106
350
|
const missing = results
|
|
107
351
|
.filter((r) => !r.available)
|
|
108
|
-
.map((r) => ({
|
|
352
|
+
.map((r) => ({
|
|
353
|
+
type: r.type,
|
|
354
|
+
command: r.command,
|
|
355
|
+
reason: r.reason,
|
|
356
|
+
installHint: r.installHint,
|
|
357
|
+
}));
|
|
109
358
|
const summary = missing.length === 0
|
|
110
359
|
? `${available.length}/${results.length} adapters available`
|
|
111
360
|
: `${available.length}/${results.length} adapters available; missing: ${missing.map((m) => m.type).join(", ")}`;
|
|
@@ -135,10 +384,27 @@ export function createServer() {
|
|
|
135
384
|
args: z.array(z.string()).optional().describe("Additional arguments to pass to the adapter"),
|
|
136
385
|
launchDefaults: z.record(z.string(), z.unknown()).optional().describe("Default launch configuration properties"),
|
|
137
386
|
installHint: z.string().optional().describe("Hint shown when adapter is not installed"),
|
|
387
|
+
transport: z.enum(["stdio", "tcp"]).optional().describe("DAP transport used by the adapter"),
|
|
388
|
+
portMatcher: z.string().optional().describe("Regex whose first capture group is the TCP port"),
|
|
138
389
|
scope: z.enum(["global", "project"]).optional().describe("Where to persist the adapter config (default: project)"),
|
|
139
|
-
},
|
|
390
|
+
}, {
|
|
391
|
+
readOnlyHint: false,
|
|
392
|
+
destructiveHint: true,
|
|
393
|
+
idempotentHint: true,
|
|
394
|
+
openWorldHint: false,
|
|
395
|
+
}, async ({ type, program, runtime, args, launchDefaults, installHint, transport, portMatcher, scope }) => {
|
|
140
396
|
try {
|
|
141
|
-
|
|
397
|
+
await registryReady;
|
|
398
|
+
const config = {
|
|
399
|
+
type,
|
|
400
|
+
program,
|
|
401
|
+
runtime,
|
|
402
|
+
args,
|
|
403
|
+
launchDefaults,
|
|
404
|
+
installHint,
|
|
405
|
+
transport,
|
|
406
|
+
portMatcher,
|
|
407
|
+
};
|
|
142
408
|
const saveScope = scope ?? "project";
|
|
143
409
|
registry.register(config, saveScope);
|
|
144
410
|
await registry.save(saveScope);
|
|
@@ -161,30 +427,33 @@ export function createServer() {
|
|
|
161
427
|
// ────────────────────────────────────────────────────────────────────────────
|
|
162
428
|
// 3. debug_launch
|
|
163
429
|
// ────────────────────────────────────────────────────────────────────────────
|
|
164
|
-
server.tool("debug_launch", "Launch
|
|
430
|
+
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
431
|
adapter: z.string().describe("Adapter type to use (e.g. 'python', 'node')"),
|
|
166
|
-
program: z.string().describe("Path to the program to debug"),
|
|
432
|
+
program: z.string().optional().describe("Path to the program to debug; may instead be supplied through launchConfig"),
|
|
167
433
|
args: z.array(z.string()).optional().describe("Program arguments"),
|
|
168
434
|
cwd: z.string().optional().describe("Working directory for the program"),
|
|
169
435
|
env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
|
|
436
|
+
launchConfig: z
|
|
437
|
+
.record(z.string(), z.unknown())
|
|
438
|
+
.optional()
|
|
439
|
+
.describe("Adapter-specific DAP launch properties; explicit common fields take precedence"),
|
|
170
440
|
stopOnEntry: z
|
|
171
441
|
.boolean()
|
|
172
442
|
.optional()
|
|
173
443
|
.describe("Stop on program entry. Defaults to false when breakpoints are provided (run to first breakpoint), true otherwise."),
|
|
174
444
|
breakpoints: z
|
|
175
|
-
.array(
|
|
176
|
-
file: z.string().describe("Source file path"),
|
|
177
|
-
line: z.number().describe("Line number"),
|
|
178
|
-
condition: z.string().optional(),
|
|
179
|
-
hitCondition: z.string().optional(),
|
|
180
|
-
logMessage: z.string().optional(),
|
|
181
|
-
}))
|
|
445
|
+
.array(breakpointInputSchema)
|
|
182
446
|
.optional()
|
|
183
447
|
.describe("Breakpoints to set before launching"),
|
|
184
448
|
exceptionBreakpoints: z
|
|
185
449
|
.array(z.string())
|
|
186
450
|
.optional()
|
|
187
451
|
.describe("Exception filter IDs (e.g. ['uncaughtExceptions'])"),
|
|
452
|
+
waitForStop: z
|
|
453
|
+
.boolean()
|
|
454
|
+
.optional()
|
|
455
|
+
.describe("Wait for stopped/terminated outcome; defaults to true when a stop is expected"),
|
|
456
|
+
timeout: z.number().positive().optional().describe("Wait timeout in milliseconds (default: 30000)"),
|
|
188
457
|
adapterConfig: z
|
|
189
458
|
.object({
|
|
190
459
|
type: z.string(),
|
|
@@ -198,11 +467,14 @@ export function createServer() {
|
|
|
198
467
|
})
|
|
199
468
|
.optional()
|
|
200
469
|
.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
|
|
470
|
+
}, async ({ adapter, program, args, cwd, env, launchConfig, stopOnEntry, breakpoints, exceptionBreakpoints, waitForStop, timeout, adapterConfig, }) => {
|
|
471
|
+
// Default: run to a configured breakpoint/exception; otherwise stop at entry.
|
|
472
|
+
const expectsConfiguredStop = (breakpoints !== undefined && breakpoints.length > 0) ||
|
|
473
|
+
(exceptionBreakpoints !== undefined && exceptionBreakpoints.length > 0);
|
|
474
|
+
const shouldStop = stopOnEntry ?? !expectsConfiguredStop;
|
|
204
475
|
let session;
|
|
205
476
|
try {
|
|
477
|
+
await registryReady;
|
|
206
478
|
const config = adapterConfig ??
|
|
207
479
|
registry.get(adapter);
|
|
208
480
|
if (!config) {
|
|
@@ -221,25 +493,15 @@ export function createServer() {
|
|
|
221
493
|
sessions.add(session);
|
|
222
494
|
// Initialize (waits for the initialized event internally)
|
|
223
495
|
await session.initialize();
|
|
224
|
-
|
|
225
|
-
const launchArgs = {
|
|
226
|
-
...config.launchDefaults,
|
|
496
|
+
const launchArgs = await buildLaunchArgs(config, {
|
|
227
497
|
program,
|
|
498
|
+
args,
|
|
499
|
+
cwd,
|
|
500
|
+
env,
|
|
501
|
+
launchConfig,
|
|
228
502
|
stopOnEntry: shouldStop,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
launchArgs.args = args;
|
|
232
|
-
if (cwd !== undefined)
|
|
233
|
-
launchArgs.cwd = cwd;
|
|
234
|
-
if (env !== undefined)
|
|
235
|
-
launchArgs.env = env;
|
|
236
|
-
// Adapter-specific arg normalization. delve's `cwd` is the debuggee's
|
|
237
|
-
// working directory; the *build* (go build) runs from delve's own
|
|
238
|
-
// process cwd, controlled by `dlvCwd`. Mirror cwd → dlvCwd so a
|
|
239
|
-
// user-supplied cwd is found by `go build` looking for go.mod.
|
|
240
|
-
if (config.type === "go" && cwd !== undefined && launchArgs.dlvCwd === undefined) {
|
|
241
|
-
launchArgs.dlvCwd = cwd;
|
|
242
|
-
}
|
|
503
|
+
sourceFiles: breakpoints?.map((bp) => bp.file),
|
|
504
|
+
});
|
|
243
505
|
// Send launch first (don't await — real adapters like debugpy block
|
|
244
506
|
// the launch response until configurationDone arrives)
|
|
245
507
|
const launchPromise = session.launch(launchArgs);
|
|
@@ -252,23 +514,7 @@ export function createServer() {
|
|
|
252
514
|
// initialize response; others (debugpy) only emit it after receiving
|
|
253
515
|
// launch — that's why we fire launch first and wait for initialized here.
|
|
254
516
|
await session.waitForInitialized(10000);
|
|
255
|
-
|
|
256
|
-
if (breakpoints && breakpoints.length > 0) {
|
|
257
|
-
const byFile = new Map();
|
|
258
|
-
for (const bp of breakpoints) {
|
|
259
|
-
const existing = byFile.get(bp.file) ?? [];
|
|
260
|
-
existing.push(bp);
|
|
261
|
-
byFile.set(bp.file, existing);
|
|
262
|
-
}
|
|
263
|
-
for (const [file, bps] of byFile) {
|
|
264
|
-
await session.setBreakpoints(file, bps.map((b) => ({
|
|
265
|
-
line: b.line,
|
|
266
|
-
condition: b.condition,
|
|
267
|
-
hitCondition: b.hitCondition,
|
|
268
|
-
logMessage: b.logMessage,
|
|
269
|
-
})));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
517
|
+
const breakpointResults = await applyBreakpoints(session, breakpoints);
|
|
272
518
|
// Set exception breakpoints
|
|
273
519
|
if (exceptionBreakpoints && exceptionBreakpoints.length > 0) {
|
|
274
520
|
await session.setExceptionBreakpoints(exceptionBreakpoints);
|
|
@@ -277,24 +523,15 @@ export function createServer() {
|
|
|
277
523
|
await session.configurationDone();
|
|
278
524
|
// Now await the launch response
|
|
279
525
|
await launchPromise;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
stateData = (await session.collectState());
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
stateData = { status: session.state };
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
stateData = { status: session.state };
|
|
292
|
-
}
|
|
526
|
+
const shouldWait = waitForStop ?? (shouldStop || expectsConfiguredStop);
|
|
527
|
+
const stateData = shouldWait
|
|
528
|
+
? await awaitFirstStop(session, timeout ?? 30000)
|
|
529
|
+
: { status: session.state };
|
|
293
530
|
return {
|
|
294
531
|
content: [
|
|
295
532
|
{
|
|
296
533
|
type: "text",
|
|
297
|
-
text: JSON.stringify({ sessionId: id, ...stateData }, null, 2),
|
|
534
|
+
text: JSON.stringify({ sessionId: id, breakpoints: breakpointResults, ...stateData }, null, 2),
|
|
298
535
|
},
|
|
299
536
|
],
|
|
300
537
|
};
|
|
@@ -313,12 +550,25 @@ export function createServer() {
|
|
|
313
550
|
// ────────────────────────────────────────────────────────────────────────────
|
|
314
551
|
// 4. debug_attach
|
|
315
552
|
// ────────────────────────────────────────────────────────────────────────────
|
|
316
|
-
server.tool("debug_attach", "Attach a debug adapter to an already-running process.", {
|
|
553
|
+
server.tool("debug_attach", "Attach a debug adapter to an already-running process. Pass breakpoints here rather than afterwards: they bind before the debuggee resumes, which is the only reliable way to stop a process started suspended (e.g. JDWP suspend=y). With breakpoints or exception breakpoints, waits and reports stopped, terminated, or timeout.", {
|
|
317
554
|
adapter: z.string().describe("Adapter type to use"),
|
|
318
555
|
port: z.number().optional().describe("Debug port to attach to"),
|
|
319
556
|
host: z.string().optional().describe("Host to attach to (default: localhost)"),
|
|
320
557
|
processId: z.number().optional().describe("PID of process to attach to"),
|
|
321
558
|
attachConfig: z.record(z.string(), z.unknown()).optional().describe("Arbitrary attach configuration properties"),
|
|
559
|
+
breakpoints: z
|
|
560
|
+
.array(breakpointInputSchema)
|
|
561
|
+
.optional()
|
|
562
|
+
.describe("Breakpoints to bind before the debuggee resumes"),
|
|
563
|
+
exceptionBreakpoints: z
|
|
564
|
+
.array(z.string())
|
|
565
|
+
.optional()
|
|
566
|
+
.describe("Exception filter IDs (e.g. ['uncaughtExceptions'])"),
|
|
567
|
+
waitForStop: z
|
|
568
|
+
.boolean()
|
|
569
|
+
.optional()
|
|
570
|
+
.describe("Wait for stopped/terminated outcome; defaults to true when breakpoints or exception breakpoints are given"),
|
|
571
|
+
timeout: z.number().positive().optional().describe("Wait timeout in milliseconds (default: 30000)"),
|
|
322
572
|
adapterConfig: z
|
|
323
573
|
.object({
|
|
324
574
|
type: z.string(),
|
|
@@ -332,9 +582,12 @@ export function createServer() {
|
|
|
332
582
|
})
|
|
333
583
|
.optional()
|
|
334
584
|
.describe("Override adapter config inline"),
|
|
335
|
-
}, async ({ adapter, port, host, processId, attachConfig, adapterConfig }) => {
|
|
585
|
+
}, async ({ adapter, port, host, processId, attachConfig, breakpoints, exceptionBreakpoints, waitForStop, timeout, adapterConfig, }) => {
|
|
586
|
+
const expectsConfiguredStop = (breakpoints !== undefined && breakpoints.length > 0) ||
|
|
587
|
+
(exceptionBreakpoints !== undefined && exceptionBreakpoints.length > 0);
|
|
336
588
|
let session;
|
|
337
589
|
try {
|
|
590
|
+
await registryReady;
|
|
338
591
|
const config = adapterConfig ?? registry.get(adapter);
|
|
339
592
|
if (!config) {
|
|
340
593
|
return {
|
|
@@ -368,13 +621,24 @@ export function createServer() {
|
|
|
368
621
|
// will still throw if attach fails.
|
|
369
622
|
attachPromise.catch(() => { });
|
|
370
623
|
await session.waitForInitialized(10000);
|
|
624
|
+
const breakpointResults = await applyBreakpoints(session, breakpoints);
|
|
625
|
+
if (exceptionBreakpoints && exceptionBreakpoints.length > 0) {
|
|
626
|
+
await session.setExceptionBreakpoints(exceptionBreakpoints);
|
|
627
|
+
}
|
|
371
628
|
await session.configurationDone();
|
|
372
629
|
await attachPromise;
|
|
630
|
+
const stateData = waitForStop ?? expectsConfiguredStop
|
|
631
|
+
? await awaitFirstStop(session, timeout ?? 30000)
|
|
632
|
+
: { status: session.state };
|
|
373
633
|
return {
|
|
374
634
|
content: [
|
|
375
635
|
{
|
|
376
636
|
type: "text",
|
|
377
|
-
text: JSON.stringify({
|
|
637
|
+
text: JSON.stringify({
|
|
638
|
+
sessionId: id,
|
|
639
|
+
breakpoints: breakpointResults.length > 0 ? breakpointResults : undefined,
|
|
640
|
+
...stateData,
|
|
641
|
+
}, null, 2),
|
|
378
642
|
},
|
|
379
643
|
],
|
|
380
644
|
};
|
|
@@ -455,32 +719,40 @@ export function createServer() {
|
|
|
455
719
|
threadId: z.number().optional().describe("Thread ID (uses stopped thread if omitted)"),
|
|
456
720
|
depth: z.number().optional().describe("Stack trace depth (default: 10)"),
|
|
457
721
|
variableDepth: z.number().optional().describe("Variable expansion depth (default: 1)"),
|
|
722
|
+
scopes: z
|
|
723
|
+
.array(z.string())
|
|
724
|
+
.optional()
|
|
725
|
+
.describe("Scope names to fetch, e.g. ['Registers']. Default: every scope except CPU registers; skipped ones are listed in omittedScopes"),
|
|
726
|
+
detail: detailSchema
|
|
727
|
+
.optional()
|
|
728
|
+
.describe("'full' (default) or 'compact' (frame-local scopes only, slimmer JSON)"),
|
|
458
729
|
timeout: z
|
|
459
730
|
.number()
|
|
460
731
|
.optional()
|
|
461
732
|
.describe("If running, wait up to this many milliseconds for the program to stop (default: 0 = don't wait)"),
|
|
462
|
-
},
|
|
733
|
+
}, {
|
|
734
|
+
readOnlyHint: true,
|
|
735
|
+
destructiveHint: false,
|
|
736
|
+
idempotentHint: true,
|
|
737
|
+
openWorldHint: false,
|
|
738
|
+
}, async ({ sessionId, threadId, depth, variableDepth, scopes, detail, timeout }) => {
|
|
463
739
|
try {
|
|
464
740
|
const session = sessions.resolve(sessionId);
|
|
465
741
|
const waitMs = timeout ?? 0;
|
|
742
|
+
const stateDetail = detail ?? "full";
|
|
743
|
+
if (waitMs > 0 && session.state === "running") {
|
|
744
|
+
await session.waitForStopOrTermination(waitMs);
|
|
745
|
+
}
|
|
466
746
|
if (session.state === "stopped") {
|
|
467
|
-
|
|
747
|
+
// Read-only: output stays unreported so a later step still carries it.
|
|
748
|
+
const state = await session.collectState(threadId, depth ?? 10, variableDepth ?? 1, {
|
|
749
|
+
scopes,
|
|
750
|
+
localsOnly: stateDetail === "compact",
|
|
751
|
+
});
|
|
468
752
|
return {
|
|
469
|
-
content: [{ type: "text", text:
|
|
753
|
+
content: [{ type: "text", text: renderState(state, stateDetail) }],
|
|
470
754
|
};
|
|
471
755
|
}
|
|
472
|
-
if (waitMs > 0 && session.state === "running") {
|
|
473
|
-
try {
|
|
474
|
-
await session.waitForStopped(waitMs);
|
|
475
|
-
const state = await session.collectState(threadId, depth ?? 10, variableDepth ?? 1);
|
|
476
|
-
return {
|
|
477
|
-
content: [{ type: "text", text: JSON.stringify(state, null, 2) }],
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
catch {
|
|
481
|
-
// Timed out — fall through to return running status
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
756
|
const { output } = session.getOutput();
|
|
485
757
|
return {
|
|
486
758
|
content: [
|
|
@@ -547,20 +819,29 @@ export function createServer() {
|
|
|
547
819
|
.enum(["continue", "next", "stepIn", "stepOut", "pause"])
|
|
548
820
|
.describe("Execution control action"),
|
|
549
821
|
threadId: z.number().optional().describe("Thread ID (uses stopped thread if omitted)"),
|
|
822
|
+
detail: detailSchema
|
|
823
|
+
.optional()
|
|
824
|
+
.describe("'compact' (default: frame-local scopes, short stack, slimmer JSON) or 'full' (same shape as get_state)"),
|
|
550
825
|
timeout: z
|
|
551
826
|
.number()
|
|
552
827
|
.optional()
|
|
553
828
|
.describe("Milliseconds to wait for the program to stop after the action (default: 30000)"),
|
|
554
|
-
}, async ({ sessionId, action, threadId, timeout }) => {
|
|
829
|
+
}, async ({ sessionId, action, threadId, detail, timeout }) => {
|
|
555
830
|
try {
|
|
556
831
|
const session = sessions.resolve(sessionId);
|
|
557
832
|
const waitMs = timeout ?? 30000;
|
|
558
833
|
const startTime = Date.now();
|
|
834
|
+
const stateDetail = detail ?? "compact";
|
|
835
|
+
const collectSteppedState = () => session.collectState(threadId, stateDetail === "compact" ? COMPACT_STACK_DEPTH : 10, 1, {
|
|
836
|
+
localsOnly: stateDetail === "compact",
|
|
837
|
+
consumeOutput: true,
|
|
838
|
+
});
|
|
559
839
|
// If already stopped and asking to pause, just return current state
|
|
560
840
|
if (action === "pause" && session.state === "stopped") {
|
|
561
|
-
const state = await session.collectState(threadId);
|
|
562
841
|
return {
|
|
563
|
-
content: [
|
|
842
|
+
content: [
|
|
843
|
+
{ type: "text", text: renderState(await collectSteppedState(), stateDetail) },
|
|
844
|
+
],
|
|
564
845
|
};
|
|
565
846
|
}
|
|
566
847
|
switch (action) {
|
|
@@ -580,20 +861,13 @@ export function createServer() {
|
|
|
580
861
|
await session.pause(threadId);
|
|
581
862
|
break;
|
|
582
863
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
catch (err) {
|
|
587
|
-
// Timeout or terminated
|
|
864
|
+
const outcome = await session.waitForStopOrTermination(waitMs);
|
|
865
|
+
if (outcome.status !== "stopped") {
|
|
588
866
|
const elapsed = Date.now() - startTime;
|
|
589
|
-
if (
|
|
590
|
-
const { output } = session.getOutput();
|
|
867
|
+
if (outcome.status === "terminated") {
|
|
591
868
|
return {
|
|
592
869
|
content: [
|
|
593
|
-
{
|
|
594
|
-
type: "text",
|
|
595
|
-
text: JSON.stringify({ status: "terminated", output: output.map((e) => e.text) }, null, 2),
|
|
596
|
-
},
|
|
870
|
+
{ type: "text", text: JSON.stringify(await terminatedReport(session)) },
|
|
597
871
|
],
|
|
598
872
|
};
|
|
599
873
|
}
|
|
@@ -606,20 +880,10 @@ export function createServer() {
|
|
|
606
880
|
],
|
|
607
881
|
};
|
|
608
882
|
}
|
|
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
|
-
const state = await session.collectState(threadId);
|
|
621
883
|
return {
|
|
622
|
-
content: [
|
|
884
|
+
content: [
|
|
885
|
+
{ type: "text", text: renderState(await collectSteppedState(), stateDetail) },
|
|
886
|
+
],
|
|
623
887
|
};
|
|
624
888
|
}
|
|
625
889
|
catch (err) {
|
|
@@ -643,6 +907,11 @@ export function createServer() {
|
|
|
643
907
|
.number()
|
|
644
908
|
.optional()
|
|
645
909
|
.describe("Return only entries with seq > since. Pass the `nextSince` value from a prior call to fetch incremental output."),
|
|
910
|
+
}, {
|
|
911
|
+
readOnlyHint: false,
|
|
912
|
+
destructiveHint: true,
|
|
913
|
+
idempotentHint: false,
|
|
914
|
+
openWorldHint: false,
|
|
646
915
|
}, async ({ sessionId, category, clear, since }) => {
|
|
647
916
|
try {
|
|
648
917
|
const session = sessions.resolve(sessionId);
|
|
@@ -746,19 +1015,10 @@ export function createServer() {
|
|
|
746
1015
|
await newSession.configurationDone();
|
|
747
1016
|
await launchPromise;
|
|
748
1017
|
const stopOnEntry = launchConfig.stopOnEntry !== false;
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
stateData = (await newSession.collectState());
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
stateData = { status: newSession.state };
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
stateData = { status: newSession.state };
|
|
761
|
-
}
|
|
1018
|
+
const shouldWait = stopOnEntry || savedBreakpoints.size > 0;
|
|
1019
|
+
const stateData = shouldWait
|
|
1020
|
+
? await awaitFirstStop(newSession, 30000)
|
|
1021
|
+
: { status: newSession.state };
|
|
762
1022
|
return {
|
|
763
1023
|
content: [
|
|
764
1024
|
{
|
|
@@ -804,6 +1064,10 @@ export function createServer() {
|
|
|
804
1064
|
.number()
|
|
805
1065
|
.optional()
|
|
806
1066
|
.describe("Milliseconds to wait for the breakpoint to be hit (default: 30000)"),
|
|
1067
|
+
launchConfig: z
|
|
1068
|
+
.record(z.string(), z.unknown())
|
|
1069
|
+
.optional()
|
|
1070
|
+
.describe("Adapter-specific DAP launch fields"),
|
|
807
1071
|
adapterConfig: z
|
|
808
1072
|
.object({
|
|
809
1073
|
type: z.string(),
|
|
@@ -819,6 +1083,7 @@ export function createServer() {
|
|
|
819
1083
|
.describe("Override adapter config inline"),
|
|
820
1084
|
}, async (opts) => {
|
|
821
1085
|
try {
|
|
1086
|
+
await registryReady;
|
|
822
1087
|
const result = await runDebugInspect(registry, sessions, opts);
|
|
823
1088
|
return {
|
|
824
1089
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
@@ -836,6 +1101,11 @@ export function createServer() {
|
|
|
836
1101
|
// ────────────────────────────────────────────────────────────────────────────
|
|
837
1102
|
server.tool("list_breakpoints", "List the breakpoints currently registered with a session, grouped by source file. Includes regular breakpoints, conditional breakpoints, and logpoints.", {
|
|
838
1103
|
sessionId: z.string().optional().describe("Session ID (omit if only one session is active)"),
|
|
1104
|
+
}, {
|
|
1105
|
+
readOnlyHint: true,
|
|
1106
|
+
destructiveHint: false,
|
|
1107
|
+
idempotentHint: true,
|
|
1108
|
+
openWorldHint: false,
|
|
839
1109
|
}, async ({ sessionId }) => {
|
|
840
1110
|
try {
|
|
841
1111
|
const session = sessions.resolve(sessionId);
|
|
@@ -897,6 +1167,7 @@ export function createServer() {
|
|
|
897
1167
|
.describe("Override adapter config inline"),
|
|
898
1168
|
}, async (opts) => {
|
|
899
1169
|
try {
|
|
1170
|
+
await registryReady;
|
|
900
1171
|
const result = await runDebugRun(registry, sessions, opts);
|
|
901
1172
|
return {
|
|
902
1173
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|