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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to DebugMCP will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).

## [2.1.0] - 2026-06-23

### Added
- **Conditional breakpoints** — `add_breakpoint` now accepts an optional `condition` expression so execution only pauses when the condition evaluates to true (e.g. `i == 5`, `user.id === null`). Conditions are surfaced in `list_breakpoints` and the debug state.

### Changed
- **Renamed the companion Agent Skill from `really-debug` to `debug-live`.** It now installs at `skills/debug-live/` (e.g. `~/.copilot/skills/debug-live/`) and is invoked with `/debug-live`. Previous `really-debug` (and `debug`) installs are cleaned up automatically on registration.

### Fixed
- **`continue`/step no longer hangs when the program runs to completion.** Detection now settles on termination of the specific session being debugged (by identity) instead of waiting for the global active session to clear, which previously hung until the timeout when a parent session (e.g. the JS debug terminal) outlived the program.

## [1.2.0] - 2026-06-04

### Added
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Let AI agents debug your code inside VS Code - set breakpoints, step through exe

## ✨ What's New in 2.0.0

- **`/really-debug` Agent Skill** — DebugMCP now ships a companion [Agent Skill](./skills/really-debug/SKILL.md) that is auto-installed into each configured harness's personal skills directory (e.g. `~/.copilot/skills/really-debug/`). Invoke it with `/really-debug` in supporting agents to load the systematic debugging workflow and trigger DebugMCP tools with the right context.
- **`/debug-live` Agent Skill** — DebugMCP now ships a companion [Agent Skill](./skills/debug-live/SKILL.md) that is auto-installed into each configured harness's personal skills directory (e.g. `~/.copilot/skills/debug-live/`). Invoke it with `/debug-live` in supporting agents to load the systematic debugging workflow and trigger DebugMCP tools with the right context.
- **Robust debugging via the VS Code Testing API** — `start_debugging` with a `testName` now uses the VS Code Testing API to discover and launch the test, replacing the previous best-effort path. This works reliably across language test runners that integrate with the Testing API (pytest, Jest/Vitest, Java, .NET, Go, etc.) and produces consistent breakpoint hits inside individual test cases.

## 🚀 Quick Install
Expand Down Expand Up @@ -57,7 +57,7 @@ DebugMCP is an MCP server that gives AI coding agents full control over the VS C
| **step_out** | Step out of the current function | None |
| **continue_execution** | Continue until next breakpoint | None |
| **restart_debugging** | Restart the current debug session | None |
| **add_breakpoint** | Add a breakpoint at a specific line | `fileFullPath` (required)<br>`lineContent` (required) |
| **add_breakpoint** | Add a breakpoint at a specific line (optionally conditional) | `fileFullPath` (required)<br>`lineContent` (required)<br>`condition` (optional) |
| **remove_breakpoint** | Remove a breakpoint from a specific line | `fileFullPath` (required)<br>`line` (required) |
| **clear_all_breakpoints** | Remove all breakpoints at once | None |
| **list_breakpoints** | List all active breakpoints | None |
Expand All @@ -67,7 +67,7 @@ DebugMCP is an MCP server that gives AI coding agents full control over the VS C
> **Note:** The MCP server intentionally exposes **tools only** — no procedural
> instructions, no documentation resources. Workflow guidance (when to debug, how to
> structure a root-cause investigation, language-specific quirks) lives in the companion
> [DebugMCP Agent Skill](./skills/really-debug/SKILL.md) so it can be loaded into an
> [DebugMCP Agent Skill](./skills/debug-live/SKILL.md) so it can be loaded into an
> agent's prompt context independently of the MCP capability surface.

### 🎯 Debugging Best Practices
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/debugMCPServer.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ AI Agent (MCP Client)

`DebugMCPServer` exposes **tools only**. Procedural workflow guidance (when to debug,
how to structure a root-cause investigation, language-specific quirks) lives in the
companion Agent Skill at `skills/really-debug/SKILL.md`, not in tool descriptions or MCP
companion Agent Skill at `skills/debug-live/SKILL.md`, not in tool descriptions or MCP
resources. This separation matches modern agent ecosystems where MCP servers provide
*capabilities* and skills provide *procedural knowledge* an agent loads as context.

Expand All @@ -58,7 +58,7 @@ Each request creates a new stateless `StreamableHTTPServerTransport` instance th
- Class definition: `src/debugMCPServer.ts`
- Tool registration: `setupTools()` method (uses `McpServer.registerTool()`)
- Server startup: `start()` method (creates express app with `/mcp` route)
- Agent Skill (companion, not part of the MCP surface): `skills/really-debug/SKILL.md`
- Agent Skill (companion, not part of the MCP surface): `skills/debug-live/SKILL.md`

## Exposed Tools

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "debugmcpextension",
"displayName": "DebugMCP",
"description": "Let AI agents debug your code inside VS Code — breakpoints, step-through execution, variable inspection, and expression evaluation. Automatically exposes itself as an MCP (Model Context Protocol) server for seamless integration with AI assistants.",
"version": "2.0.1",
"version": "2.1.0",
"publisher": "ozzafar",
"author": {
"name": "Oz Zafar",
Expand Down Expand Up @@ -79,9 +79,17 @@
"description": "Port number for the DebugMCP server"
},
"debugmcp.bindHost": {
"type": ["string", "array"],
"items": { "type": "string" },
"default": ["127.0.0.1", "::1"],
"type": [
"string",
"array"
],
"items": {
"type": "string"
},
"default": [
"127.0.0.1",
"::1"
],
"markdownDescription": "Network interface(s) the DebugMCP HTTP server binds to. **Defaults to `[\"127.0.0.1\", \"::1\"]` (IPv4 + IPv6 loopback only).** Accepts a single string or an array of strings. ⚠️ **Security warning:** changing this to `0.0.0.0` or a LAN address exposes the unauthenticated MCP debugger — including arbitrary code execution via `evaluate_expression` and `start_debugging` — to every host on the network. Only change this if you fully understand the risk."
}
}
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: really-debug
name: debug-live
description: Drive an interactive VS Code debugger to investigate bugs, failing tests, wrong/null variable values, unexpected runtime behavior, and other "it doesn't work" reports. Use this skill whenever speculation about runtime behavior would be cheaper to *verify* by stepping through the code than to reason about. Pairs with the DebugMCP MCP server, which exposes the underlying breakpoint / step / inspect tools.
license: MIT
allowed-tools:
Expand Down
9 changes: 5 additions & 4 deletions src/debugMCPServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
*
* Tools only. Procedural workflow guidance (when to debug, how to perform
* root-cause analysis, language-specific quirks) lives in the companion
* Agent Skill at `skills/really-debug/` — the MCP surface itself only describes
* Agent Skill at `skills/debug-live/` — the MCP surface itself only describes
* what each tool does, not how to use them together.
*/
/**
Expand Down Expand Up @@ -155,7 +155,7 @@ export class DebugMCPServer {
* Tool descriptions are intentionally terse and behavioral. Procedural
* guidance (when to use which tool, how to perform root-cause analysis,
* language-specific quirks) lives in the companion Agent Skill at
* `skills/really-debug/SKILL.md`.
* `skills/debug-live/SKILL.md`.
*/
private setupTools(server: McpServer) {
// Start debugging tool
Expand Down Expand Up @@ -231,12 +231,13 @@ export class DebugMCPServer {

// Add breakpoint tool
server.registerTool('add_breakpoint', {
description: 'Set a breakpoint to pause execution at a critical line of code. Essential for debugging: pause before potential errors, examine state at decision points, or verify code paths. Breakpoints let you inspect variables and control flow at exact moments.',
description: 'Set a breakpoint to pause execution at a critical line of code. Essential for debugging: pause before potential errors, examine state at decision points, or verify code paths. Breakpoints let you inspect variables and control flow at exact moments. Provide an optional condition to create a conditional breakpoint that only pauses when the expression evaluates to true (e.g. "i == 5" or "user.id === null").',
inputSchema: {
fileFullPath: z.string().describe('Full path to the file'),
lineContent: z.string().describe('Line content'),
condition: z.string().optional().describe('Optional condition expression. When provided, execution only pauses if this expression evaluates to true at the breakpoint location.'),
},
}, async (args: { fileFullPath: string; lineContent: string }) => {
}, async (args: { fileFullPath: string; lineContent: string; condition?: string }) => {
const result = await this.debuggingHandler.handleAddBreakpoint(args);
return { content: [{ type: 'text' as const, text: result }] };
});
Expand Down
15 changes: 10 additions & 5 deletions src/debuggingExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface IDebuggingExecutor {
stepOut(): Promise<void>;
continue(): Promise<void>;
restart(): Promise<void>;
addBreakpoint(uri: vscode.Uri, line: number): Promise<void>;
addBreakpoint(uri: vscode.Uri, line: number, condition?: string): Promise<void>;
removeBreakpoint(uri: vscode.Uri, line: number): Promise<void>;
getCurrentDebugState(numNextLines: number): Promise<DebugState>;
getVariables(frameId: number, scope?: 'local' | 'global' | 'all'): Promise<any>;
Expand Down Expand Up @@ -286,12 +286,16 @@ export class DebuggingExecutor implements IDebuggingExecutor {
}

/**
* Add a breakpoint at specified location
* Add a breakpoint at specified location. An optional condition makes it a
* conditional breakpoint that only pauses execution when the expression
* evaluates to true.
*/
public async addBreakpoint(uri: vscode.Uri, line: number): Promise<void> {
public async addBreakpoint(uri: vscode.Uri, line: number, condition?: string): Promise<void> {
try {
const breakpoint = new vscode.SourceBreakpoint(
new vscode.Location(uri, new vscode.Position(line - 1, 0))
new vscode.Location(uri, new vscode.Position(line - 1, 0)),
true,
condition
);
vscode.debug.addBreakpoints([breakpoint]);
} catch (error) {
Expand Down Expand Up @@ -361,7 +365,8 @@ export class DebuggingExecutor implements IDebuggingExecutor {
.map(bp => {
const fileName = bp.location.uri.fsPath.split(/[/\\]/).pop() || 'unknown';
const line = bp.location.range.start.line + 1;
return `${fileName}:${line}`;
const base = `${fileName}:${line}`;
return bp.condition ? `${base} [when: ${bp.condition}]` : base;
});
state.updateBreakpoints(formattedBreakpoints);

Expand Down
19 changes: 11 additions & 8 deletions src/debuggingHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface IDebuggingHandler {
handleStepOut(): Promise<string>;
handleContinue(): Promise<string>;
handleRestart(): Promise<string>;
handleAddBreakpoint(args: { fileFullPath: string; lineContent: string }): Promise<string>;
handleAddBreakpoint(args: { fileFullPath: string; lineContent: string; condition?: string }): Promise<string>;
handleRemoveBreakpoint(args: { fileFullPath: string; line: number }): Promise<string>;
handleClearAllBreakpoints(): Promise<string>;
handleListBreakpoints(): Promise<string>;
Expand Down Expand Up @@ -271,10 +271,11 @@ export class DebuggingHandler implements IDebuggingHandler {
}

/**
* Add a breakpoint at specified location
* Add a breakpoint at specified location. An optional condition makes it a
* conditional breakpoint that only pauses when the expression is true.
*/
public async handleAddBreakpoint(args: { fileFullPath: string; lineContent: string }): Promise<string> {
const { fileFullPath, lineContent } = args;
public async handleAddBreakpoint(args: { fileFullPath: string; lineContent: string; condition?: string }): Promise<string> {
const { fileFullPath, lineContent, condition } = args;

try {
// Find the line number containing the line content
Expand All @@ -297,14 +298,15 @@ export class DebuggingHandler implements IDebuggingHandler {

// Add breakpoints to all matching lines
for (const lineNumber of matchingLineNumbers) {
await this.executor.addBreakpoint(uri, lineNumber);
await this.executor.addBreakpoint(uri, lineNumber, condition);
}

const conditionInfo = condition ? ` (condition: ${condition})` : '';
if (matchingLineNumbers.length === 1) {
return `Breakpoint added at ${fileFullPath}:${matchingLineNumbers[0]}`;
return `Breakpoint added at ${fileFullPath}:${matchingLineNumbers[0]}${conditionInfo}`;
} else {
const linesList = matchingLineNumbers.join(', ');
return `Breakpoints added at ${matchingLineNumbers.length} locations in ${fileFullPath}: lines ${linesList}`;
return `Breakpoints added at ${matchingLineNumbers.length} locations in ${fileFullPath}: lines ${linesList}${conditionInfo}`;
}
} catch (error) {
throw new Error(`Error adding breakpoint: ${error}`);
Expand Down Expand Up @@ -357,7 +359,8 @@ export class DebuggingHandler implements IDebuggingHandler {
if (bp instanceof vscode.SourceBreakpoint) {
const fileName = bp.location.uri.fsPath.split(/[/\\]/).pop();
const line = bp.location.range.start.line + 1;
breakpointList += `${index + 1}. ${fileName}:${line}\n`;
const conditionInfo = bp.condition ? ` (condition: ${bp.condition})` : '';
breakpointList += `${index + 1}. ${fileName}:${line}${conditionInfo}\n`;
} else if (bp instanceof vscode.FunctionBreakpoint) {
breakpointList += `${index + 1}. Function: ${bp.functionName}\n`;
}
Expand Down
30 changes: 18 additions & 12 deletions src/utils/agentConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class AgentConfigurationManager {
* Path to the debugmcp skill bundled with the extension.
*/
private getBundledSkillPath(): string {
return path.join(this.context.extensionPath, 'skills', 'really-debug');
return path.join(this.context.extensionPath, 'skills', 'debug-live');
}

/**
Expand All @@ -220,23 +220,29 @@ export class AgentConfigurationManager {
}

const skillsDir = this.getSkillsDirForAgent(agent);
const destination = path.join(skillsDir, 'really-debug');
const destination = path.join(skillsDir, 'debug-live');

try {
await fs.promises.mkdir(skillsDir, { recursive: true });
await fs.promises.cp(bundledSkillPath, destination, { recursive: true, force: true });
console.log(`Successfully registered debugmcp skill for ${agent.name} at ${destination}`);

// Back-compat cleanup: earlier 1.2.0 builds installed the skill at
// `<skillsDir>/debug`. Remove the stale copy if it's still around so
// users don't end up with two competing entries.
const legacyDestination = path.join(skillsDir, 'debug');
if (fs.existsSync(legacyDestination)) {
try {
await fs.promises.rm(legacyDestination, { recursive: true, force: true });
console.log(`Removed legacy debugmcp skill at ${legacyDestination}`);
} catch (cleanupError) {
console.warn(`Failed to remove legacy debugmcp skill at ${legacyDestination}:`, cleanupError);
// Back-compat cleanup: earlier builds installed the skill under
// different directory names (`debug` in 1.2.0, later `really-debug`).
// Remove any stale copies so users don't end up with competing
// entries alongside the current `debug-live` skill.
const legacyDestinations = [
path.join(skillsDir, 'debug'),
path.join(skillsDir, 'really-debug'),
];
for (const legacyDestination of legacyDestinations) {
if (fs.existsSync(legacyDestination)) {
try {
await fs.promises.rm(legacyDestination, { recursive: true, force: true });
console.log(`Removed legacy debugmcp skill at ${legacyDestination}`);
} catch (cleanupError) {
console.warn(`Failed to remove legacy debugmcp skill at ${legacyDestination}:`, cleanupError);
}
}
}

Expand Down
Loading