Skip to content

Commit 150612f

Browse files
committed
support configname + debug state tosting
1 parent f1c7f68 commit 150612f

5 files changed

Lines changed: 91 additions & 67 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ DebugMCP bridges the gap between professional debugging and AI-assisted developm
3535
| Tool | Description | Parameters |
3636
|------|-------------|------------|
3737
| **get_debug_instructions** | Get the debugging guide with best practices and workflow instructions | None |
38-
| **start_debugging** | Start a debug session for a source code file | `fileFullPath` (required)<br>`workingDirectory` (required)<br>`testName` (optional) |
38+
| **start_debugging** | Start a debug session for a source code file | `fileFullPath` (required)<br>`workingDirectory` (required)<br>`testName` (optional)<br>`configurationName` (optional) |
3939
| **stop_debugging** | Stop the current debug session | None |
4040
| **step_over** | Execute the next line (step over function calls) | None |
4141
| **step_into** | Step into function calls | None |

src/debugMCPServer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,12 @@ export class DebugMCPServer {
8686
'Only provide this when debugging a single test method. ' +
8787
'Leave empty to debug the entire file or test class.'
8888
),
89+
configurationName: z.string().optional().describe(
90+
'Name of a specific debug configuration from launch.json to use. ' +
91+
'Leave empty to be prompted to select a configuration interactively.'
92+
),
8993
},
90-
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string }) => {
94+
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string }) => {
9195
const result = await this.debuggingHandler.handleStartDebugging(args);
9296
return { content: [{ type: 'text' as const, text: result }] };
9397
});

src/debugState.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export class DebugState {
2424
public threadId: number | null;
2525
public frameName: string | null;
2626
public stackTrace: StackFrame[];
27-
// TODO breakpoints
27+
public configurationName: string | null;
28+
public breakpoints: string[];
2829

2930
constructor() {
3031
this.sessionActive = false;
@@ -37,6 +38,8 @@ export class DebugState {
3738
this.threadId = null;
3839
this.frameName = null;
3940
this.stackTrace = [];
41+
this.configurationName = null;
42+
this.breakpoints = [];
4043
}
4144

4245
/**
@@ -53,6 +56,8 @@ export class DebugState {
5356
this.threadId = null;
5457
this.frameName = null;
5558
this.stackTrace = [];
59+
this.configurationName = null;
60+
this.breakpoints = [];
5661
}
5762

5863
/**
@@ -119,8 +124,19 @@ export class DebugState {
119124
}
120125

121126
/**
122-
* Clone the current state
127+
* Update the configuration name
123128
*/
129+
public updateConfigurationName(configurationName: string | null): void {
130+
this.configurationName = configurationName;
131+
}
132+
133+
/**
134+
* Update breakpoints list (formatted as "fileName:line" strings)
135+
*/
136+
public updateBreakpoints(breakpoints: string[]): void {
137+
this.breakpoints = [...breakpoints];
138+
}
139+
124140
public clone(): DebugState {
125141
const cloned = new DebugState();
126142
cloned.sessionActive = this.sessionActive;
@@ -133,6 +149,52 @@ export class DebugState {
133149
cloned.threadId = this.threadId;
134150
cloned.frameName = this.frameName;
135151
cloned.stackTrace = [...this.stackTrace];
152+
cloned.configurationName = this.configurationName;
153+
cloned.breakpoints = [...this.breakpoints];
136154
return cloned;
137155
}
156+
157+
/**
158+
* Format debug state as a JSON string for structured output
159+
*/
160+
public toString(): string {
161+
const stateObject: {
162+
sessionActive: boolean;
163+
configurationName?: string | null;
164+
stackTrace?: string[];
165+
breakpoints?: string[];
166+
fileFullPath?: string | null;
167+
fileName?: string | null;
168+
currentLine?: number | null;
169+
currentLineContent?: string | null;
170+
nextLines?: string[];
171+
frameId?: number | null;
172+
threadId?: number | null;
173+
frameName?: string | null;
174+
} = {
175+
sessionActive: this.sessionActive,
176+
};
177+
178+
if (this.sessionActive) {
179+
stateObject.configurationName = this.configurationName;
180+
181+
// Compact stack trace: "functionName:line" format
182+
stateObject.stackTrace = this.stackTrace.map(frame =>
183+
`${frame.name}:${frame.line || '?'}`
184+
);
185+
186+
stateObject.breakpoints = this.breakpoints;
187+
188+
stateObject.fileFullPath = this.fileFullPath;
189+
stateObject.fileName = this.fileName;
190+
stateObject.currentLine = this.currentLine;
191+
stateObject.currentLineContent = this.currentLineContent;
192+
stateObject.nextLines = this.nextLines;
193+
stateObject.frameId = this.frameId;
194+
stateObject.threadId = this.threadId;
195+
stateObject.frameName = this.frameName;
196+
}
197+
198+
return JSON.stringify(stateObject, null, 2);
199+
}
138200
}

src/debuggingExecutor.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export class DebuggingExecutor implements IDebuggingExecutor {
166166
const activeSession = vscode.debug.activeDebugSession;
167167
if (activeSession) {
168168
state.sessionActive = true;
169+
state.updateConfigurationName(activeSession.configuration.name ?? null);
169170

170171
const activeStackItem = vscode.debug.activeStackItem;
171172
if (activeStackItem && 'frameId' in activeStackItem) {
@@ -207,6 +208,17 @@ export class DebuggingExecutor implements IDebuggingExecutor {
207208
console.log('Unable to get debug state:', error);
208209
}
209210

211+
// Populate breakpoints as compact "fileName:line" strings
212+
const breakpoints = vscode.debug.breakpoints;
213+
const formattedBreakpoints = breakpoints
214+
.filter((bp): bp is vscode.SourceBreakpoint => bp instanceof vscode.SourceBreakpoint)
215+
.map(bp => {
216+
const fileName = bp.location.uri.fsPath.split(/[/\\]/).pop() || 'unknown';
217+
const line = bp.location.range.start.line + 1;
218+
return `${fileName}:${line}`;
219+
});
220+
state.updateBreakpoints(formattedBreakpoints);
221+
210222
return state;
211223
}
212224

src/debuggingHandler.ts

Lines changed: 9 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { logger } from './utils/logger';
1010
* Interface for debugging handler operations
1111
*/
1212
export interface IDebuggingHandler {
13-
handleStartDebugging(args: { fileFullPath: string; workingDirectory: string; testName?: string }): Promise<string>;
13+
handleStartDebugging(args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string }): Promise<string>;
1414
handleStopDebugging(): Promise<string>;
1515
handleStepOver(): Promise<string>;
1616
handleStepInto(): Promise<string>;
@@ -48,11 +48,12 @@ export class DebuggingHandler implements IDebuggingHandler {
4848
fileFullPath: string;
4949
workingDirectory: string;
5050
testName?: string;
51+
configurationName?: string;
5152
}): Promise<string> {
52-
const { fileFullPath, workingDirectory, testName } = args;
53+
const { fileFullPath, workingDirectory, testName, configurationName } = args;
5354

5455
try {
55-
let selectedConfigName = await this.configManager.promptForConfiguration(workingDirectory);
56+
let selectedConfigName = configurationName ?? await this.configManager.promptForConfiguration(workingDirectory);
5657

5758
// Get debug configuration from launch.json or create default
5859
const debugConfig = await this.configManager.getDebugConfig(
@@ -75,7 +76,7 @@ export class DebuggingHandler implements IDebuggingHandler {
7576
const configInfo = selectedConfigName ? ` using configuration '${selectedConfigName}'` : ' with default configuration';
7677
const testInfo = testName ? ` (test: ${testName})` : '';
7778
const currentState = await this.executor.getCurrentDebugState(this.numNextLines);
78-
return `Debug session started successfully for: ${fileFullPath}${configInfo}${testInfo}. Current state: ${this.formatDebugState(currentState)}`;
79+
return `Debug session started successfully for: ${fileFullPath}${configInfo}${testInfo}. Current state: ${currentState.toString()}`;
7980
} else {
8081
throw new Error('Failed to start debug session. Make sure the appropriate language extension is installed.');
8182
}
@@ -137,8 +138,7 @@ export class DebuggingHandler implements IDebuggingHandler {
137138
// Wait for debugger state to change
138139
const afterState = await this.waitForStateChange(beforeState);
139140

140-
// Format the debug state as a string
141-
return this.formatDebugState(afterState);
141+
return afterState.toString();
142142
} catch (error) {
143143
throw new Error(`Error executing step over: ${error}`);
144144
}
@@ -161,8 +161,7 @@ export class DebuggingHandler implements IDebuggingHandler {
161161
// Wait for debugger state to change
162162
const afterState = await this.waitForStateChange(beforeState);
163163

164-
// Format the debug state as a string
165-
return this.formatDebugState(afterState);
164+
return afterState.toString();
166165
} catch (error) {
167166
throw new Error(`Error executing step into: ${error}`);
168167
}
@@ -185,8 +184,7 @@ export class DebuggingHandler implements IDebuggingHandler {
185184
// Wait for debugger state to change
186185
const afterState = await this.waitForStateChange(beforeState);
187186

188-
// Format the debug state as a string
189-
return this.formatDebugState(afterState);
187+
return afterState.toString();
190188
} catch (error) {
191189
throw new Error(`Error executing step out: ${error}`);
192190
}
@@ -209,10 +207,7 @@ export class DebuggingHandler implements IDebuggingHandler {
209207
// Wait for debugger state to change
210208
const afterState = await this.waitForStateChange(beforeState);
211209

212-
let result = this.formatDebugState(afterState);
213-
214-
215-
return result;
210+
return afterState.toString();
216211
} catch (error) {
217212
throw new Error(`Error executing continue: ${error}`);
218213
}
@@ -421,55 +416,6 @@ export class DebuggingHandler implements IDebuggingHandler {
421416
}
422417
}
423418

424-
/**
425-
* Format debug state as a JSON string for structured output
426-
*/
427-
private formatDebugState(state: DebugState): string {
428-
const stateObject: {
429-
sessionActive: boolean;
430-
stackTrace?: string[];
431-
breakpoints?: string[];
432-
fileFullPath?: string | null;
433-
fileName?: string | null;
434-
currentLine?: number | null;
435-
currentLineContent?: string | null;
436-
nextLines?: string[];
437-
frameId?: number | null;
438-
threadId?: number | null;
439-
frameName?: string | null;
440-
} = {
441-
sessionActive: state.sessionActive,
442-
};
443-
444-
if (state.sessionActive) {
445-
// Compact stack trace: "functionName:line" format
446-
stateObject.stackTrace = state.stackTrace.map(frame =>
447-
`${frame.name}:${frame.line || '?'}`
448-
);
449-
450-
// Compact breakpoints list: "fileName:line" format
451-
const breakpoints = this.executor.getBreakpoints();
452-
stateObject.breakpoints = breakpoints
453-
.filter((bp): bp is vscode.SourceBreakpoint => bp instanceof vscode.SourceBreakpoint)
454-
.map(bp => {
455-
const fileName = bp.location.uri.fsPath.split(/[/\\]/).pop() || 'unknown';
456-
const line = bp.location.range.start.line + 1;
457-
return `${fileName}:${line}`;
458-
});
459-
460-
stateObject.fileFullPath = state.fileFullPath;
461-
stateObject.fileName = state.fileName;
462-
stateObject.currentLine = state.currentLine;
463-
stateObject.currentLineContent = state.currentLineContent;
464-
stateObject.nextLines = state.nextLines;
465-
stateObject.frameId = state.frameId;
466-
stateObject.threadId = state.threadId;
467-
stateObject.frameName = state.frameName;
468-
}
469-
470-
return JSON.stringify(stateObject, null, 2);
471-
}
472-
473419
/**
474420
* Get current debug state
475421
*/

0 commit comments

Comments
 (0)