diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8afb0f6..b942314 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index 65ebfa2..e4084cc 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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)
`lineContent` (required) |
+| **add_breakpoint** | Add a breakpoint at a specific line (optionally conditional) | `fileFullPath` (required)
`lineContent` (required)
`condition` (optional) |
| **remove_breakpoint** | Remove a breakpoint from a specific line | `fileFullPath` (required)
`line` (required) |
| **clear_all_breakpoints** | Remove all breakpoints at once | None |
| **list_breakpoints** | List all active breakpoints | None |
@@ -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
diff --git a/docs/architecture/debugMCPServer.md b/docs/architecture/debugMCPServer.md
index 4c47c0e..3e7e97a 100644
--- a/docs/architecture/debugMCPServer.md
+++ b/docs/architecture/debugMCPServer.md
@@ -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.
@@ -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
diff --git a/package-lock.json b/package-lock.json
index 566877d..bb2bf63 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "debugmcpextension",
- "version": "2.0.1",
+ "version": "2.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "debugmcpextension",
- "version": "2.0.1",
+ "version": "2.1.0",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",
diff --git a/package.json b/package.json
index 6d4675e..067a4a5 100644
--- a/package.json
+++ b/package.json
@@ -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",
@@ -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."
}
}
diff --git a/skills/really-debug/README.md b/skills/debug-live/README.md
similarity index 100%
rename from skills/really-debug/README.md
rename to skills/debug-live/README.md
diff --git a/skills/really-debug/SKILL.md b/skills/debug-live/SKILL.md
similarity index 99%
rename from skills/really-debug/SKILL.md
rename to skills/debug-live/SKILL.md
index 1ea9e5e..660d6d9 100644
--- a/skills/really-debug/SKILL.md
+++ b/skills/debug-live/SKILL.md
@@ -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:
diff --git a/skills/really-debug/references/troubleshooting/cpp.md b/skills/debug-live/references/troubleshooting/cpp.md
similarity index 100%
rename from skills/really-debug/references/troubleshooting/cpp.md
rename to skills/debug-live/references/troubleshooting/cpp.md
diff --git a/skills/really-debug/references/troubleshooting/csharp.md b/skills/debug-live/references/troubleshooting/csharp.md
similarity index 100%
rename from skills/really-debug/references/troubleshooting/csharp.md
rename to skills/debug-live/references/troubleshooting/csharp.md
diff --git a/skills/really-debug/references/troubleshooting/go.md b/skills/debug-live/references/troubleshooting/go.md
similarity index 100%
rename from skills/really-debug/references/troubleshooting/go.md
rename to skills/debug-live/references/troubleshooting/go.md
diff --git a/skills/really-debug/references/troubleshooting/java.md b/skills/debug-live/references/troubleshooting/java.md
similarity index 100%
rename from skills/really-debug/references/troubleshooting/java.md
rename to skills/debug-live/references/troubleshooting/java.md
diff --git a/skills/really-debug/references/troubleshooting/javascript.md b/skills/debug-live/references/troubleshooting/javascript.md
similarity index 100%
rename from skills/really-debug/references/troubleshooting/javascript.md
rename to skills/debug-live/references/troubleshooting/javascript.md
diff --git a/skills/really-debug/references/troubleshooting/python.md b/skills/debug-live/references/troubleshooting/python.md
similarity index 100%
rename from skills/really-debug/references/troubleshooting/python.md
rename to skills/debug-live/references/troubleshooting/python.md
diff --git a/src/debugMCPServer.ts b/src/debugMCPServer.ts
index d356b05..30f8c07 100644
--- a/src/debugMCPServer.ts
+++ b/src/debugMCPServer.ts
@@ -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.
*/
/**
@@ -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
@@ -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 }] };
});
diff --git a/src/debuggingExecutor.ts b/src/debuggingExecutor.ts
index 2d82244..1c7d169 100644
--- a/src/debuggingExecutor.ts
+++ b/src/debuggingExecutor.ts
@@ -31,7 +31,7 @@ export interface IDebuggingExecutor {
stepOut(): Promise;
continue(): Promise;
restart(): Promise;
- addBreakpoint(uri: vscode.Uri, line: number): Promise;
+ addBreakpoint(uri: vscode.Uri, line: number, condition?: string): Promise;
removeBreakpoint(uri: vscode.Uri, line: number): Promise;
getCurrentDebugState(numNextLines: number): Promise;
getVariables(frameId: number, scope?: 'local' | 'global' | 'all'): Promise;
@@ -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 {
+ public async addBreakpoint(uri: vscode.Uri, line: number, condition?: string): Promise {
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) {
@@ -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);
diff --git a/src/debuggingHandler.ts b/src/debuggingHandler.ts
index c8e7998..b04a785 100644
--- a/src/debuggingHandler.ts
+++ b/src/debuggingHandler.ts
@@ -17,7 +17,7 @@ export interface IDebuggingHandler {
handleStepOut(): Promise;
handleContinue(): Promise;
handleRestart(): Promise;
- handleAddBreakpoint(args: { fileFullPath: string; lineContent: string }): Promise;
+ handleAddBreakpoint(args: { fileFullPath: string; lineContent: string; condition?: string }): Promise;
handleRemoveBreakpoint(args: { fileFullPath: string; line: number }): Promise;
handleClearAllBreakpoints(): Promise;
handleListBreakpoints(): Promise;
@@ -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 {
- const { fileFullPath, lineContent } = args;
+ public async handleAddBreakpoint(args: { fileFullPath: string; lineContent: string; condition?: string }): Promise {
+ const { fileFullPath, lineContent, condition } = args;
try {
// Find the line number containing the line content
@@ -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}`);
@@ -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`;
}
@@ -487,6 +490,8 @@ export class DebuggingHandler implements IDebuggingHandler {
private async waitForStateChange(beforeState: DebugState): Promise {
const timeoutMs = this.timeoutInSeconds * 1000;
const subscriptions: vscode.Disposable[] = [];
+ const operatingSession = this.executor.getActiveSession();
+ let operatingSessionTerminated = false;
try {
await new Promise(resolve => {
@@ -517,9 +522,12 @@ export class DebuggingHandler implements IDebuggingHandler {
})
);
subscriptions.push(
- vscode.debug.onDidTerminateDebugSession(() => {
+ vscode.debug.onDidTerminateDebugSession(session => {
// continue/step that runs the program to completion.
- if (!vscode.debug.activeDebugSession) {
+ if (operatingSession && session.id === operatingSession.id) {
+ operatingSessionTerminated = true;
+ settle();
+ } else if (!vscode.debug.activeDebugSession) {
settle();
}
})
@@ -537,7 +545,14 @@ export class DebuggingHandler implements IDebuggingHandler {
subscriptions.forEach(d => d.dispose());
}
- return await this.executor.getCurrentDebugState(this.numNextLines);
+ const afterState = await this.executor.getCurrentDebugState(this.numNextLines);
+ // The operating session ended (program ran to completion). A lingering
+ // parent session (e.g. the JS debug terminal) can leave a different
+ // session reported as active, so reflect termination explicitly here.
+ if (operatingSessionTerminated) {
+ afterState.sessionActive = false;
+ }
+ return afterState;
}
/**
diff --git a/src/utils/agentConfigurationManager.ts b/src/utils/agentConfigurationManager.ts
index 0dd84e2..7534265 100644
--- a/src/utils/agentConfigurationManager.ts
+++ b/src/utils/agentConfigurationManager.ts
@@ -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');
}
/**
@@ -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
- // `/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);
+ }
}
}