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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ 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/).

## [Unreleased]

### Added
- **Claude Code auto-registration** - Claude Code is now offered in the agent selection popup and configured via `~/.claude.json`'s user-scope `mcpServers` field. Claude Desktop connects via its Custom Connector UI instead of a static config file; the README's manual configuration section covers both.

## [2.3.5] - 2026-09-09

### Fixed
Expand Down
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# DebugMCP (MCP Server) - Empowering AI Agents with Operational Debugging Capabilities

Let AI agents debug your code inside VS Code - set breakpoints, step through execution, inspect variables, and evaluate expressions. Works with **Codex**, **GitHub Copilot**, **GitHub Copilot CLI**, **Cline**, **Cursor**, **Windsurf**, **Roo Code**, and any MCP-compatible assistant. Compatible with any VS Code supported coding language.
Let AI agents debug your code inside VS Code - set breakpoints, step through execution, inspect variables, and evaluate expressions. Works with **Codex**, **GitHub Copilot**, **GitHub Copilot CLI**, **Claude Code**, **Cline**, **Cursor**, **Windsurf**, **Roo Code**, and any MCP-compatible assistant. Compatible with any VS Code supported coding language.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![VS Code](https://img.shields.io/badge/VS%20Code-1.104.0+-blue.svg)](https://code.visualstudio.com/)
[![Version](https://img.shields.io/badge/version-2.3.5-green.svg)](https://github.com/microsoft/DebugMCP)
[![Version](https://img.shields.io/badge/version-2.3.6-green.svg)](https://github.com/microsoft/DebugMCP)
[![VS Marketplace](https://img.shields.io/badge/VS%20Marketplace-Install-blue.svg)](https://marketplace.visualstudio.com/items?itemName=ozzafar.debugmcpextension)

> ⭐ **If you find DebugMCP useful, please [star the repo on GitHub](https://github.com/microsoft/DebugMCP)!** It helps others discover the project and motivates continued development.
Expand Down Expand Up @@ -133,6 +133,7 @@ DebugMCP works with any MCP-compatible AI assistant. It auto-detects and offers
|-----------|:-----------------:|:-------------:|
| **GitHub Copilot** | ✅ | [See config](#github-copilot) |
| **GitHub Copilot CLI** | ✅ | [See config](#github-copilot-cli) |
| **Claude Code** | ✅ | [See config](#claude-code) |
| **Cline** | ✅ | [See config](#cline) |
| **Cursor** | ✅ | [See config](#cursor) |
| **Codex** | ✅ | [See config](#codex) |
Expand Down Expand Up @@ -214,6 +215,26 @@ Add to `~/.copilot/mcp-config.json` (`${COPILOT_HOME}/mcp-config.json` if `COPIL
}
```

#### Claude Code
Register DebugMCP with Claude Code:
```bash
claude mcp add --transport http --scope user debugmcp http://localhost:3001/mcp
```

Or add the equivalent configuration to `~/.claude.json` (top-level `mcpServers`, for cross-project user-scope access — see the [Claude Code MCP docs](https://code.claude.com/docs/en/mcp)):
```json
{
"mcpServers": {
"debugmcp": {
"type": "http",
"url": "http://localhost:3001/mcp"
}
}
}
```

> **Claude Desktop**: Claude Desktop connects to local/remote HTTP MCP servers through **Settings → Connectors → Add custom connector** rather than a static config file — paste `http://localhost:3001/mcp` there. DebugMCP doesn't auto-register with Desktop for this reason.

#### Cursor
Add to Cursor's MCP settings:
```json
Expand Down Expand Up @@ -315,7 +336,7 @@ DebugMCP exposes powerful debugger primitives (`evaluate_expression`, `start_deb
<details>
<summary><b>Which AI assistants are supported?</b></summary>

DebugMCP works with any MCP-compatible AI assistant, including **GitHub Copilot**, **GitHub Copilot CLI**, **Cline**, **Cursor**, **Codex**, **Windsurf**, **Roo Code**, **Antigravity**, and others. If your assistant supports the Model Context Protocol, it can use DebugMCP.
DebugMCP works with any MCP-compatible AI assistant, including **GitHub Copilot**, **GitHub Copilot CLI**, **Claude Code**, **Cline**, **Cursor**, **Codex**, **Windsurf**, **Roo Code**, **Antigravity**, and others. If your assistant supports the Model Context Protocol, it can use DebugMCP. Claude Desktop can also connect via its Custom Connector UI (see [Claude Code](#claude-code) in the manual configuration section).
</details>

<details>
Expand Down Expand Up @@ -429,7 +450,7 @@ The extension handles debug configurations intelligently:
- **Rust**: [rust-analyzer extension](vscode:extension/rust-lang.rust-analyzer)
- **PHP**: [PHP Debug extension](vscode:extension/xdebug.php-debug)
- **Ruby**: [Ruby extension](vscode:extension/Shopify.ruby-lsp) and the [`debug` gem](https://github.com/ruby/debug)
- MCP-compatible AI assistant (Copilot, Cline, Cursor, Codex, Windsurf, Roo Code, etc.)
- MCP-compatible AI assistant (Copilot, Claude Code, Cline, Cursor, Codex, Windsurf, Roo Code, etc.)

## Development

Expand Down
15 changes: 13 additions & 2 deletions docs/architecture/agentConfigurationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

## Purpose

Handles automatic configuration of AI coding agents (Cline, GitHub Copilot, GitHub Copilot CLI, Cursor, Codex) to connect to the DebugMCP server. Provides a seamless onboarding experience.
Handles automatic configuration of AI coding agents (Cline, GitHub Copilot, GitHub Copilot CLI, Claude Code, Cursor, Codex) to connect to the DebugMCP server. Provides a seamless onboarding experience.

## Motivation

For AI agents to use DebugMCP, they need MCP server configuration in their settings files. Rather than requiring users to manually edit JSON files, this manager auto-configures supported agents with the correct SSE endpoint.
For AI agents to use DebugMCP, they need MCP server configuration in their settings files. Rather than requiring users to manually edit JSON files, this manager auto-configures supported agents with the Streamable HTTP endpoint.

## Responsibility

Expand All @@ -28,6 +28,7 @@ Popup** from the Command Palette after correcting the JSON.
| Cline | `cline_mcp_settings.json` | `mcpServers` |
| GitHub Copilot | `mcp.json` | `servers` |
| GitHub Copilot CLI | `~/.copilot/mcp-config.json` or `${COPILOT_HOME}/mcp-config.json` | `mcpServers` |
| Claude Code | `~/.claude.json` | Top-level `mcpServers` (user scope, shared across projects) |
| Cursor | `mcp_settings.json` | `mcpServers` |
| Codex | `~/.codex/config.toml` or `${CODEX_HOME}/config.toml` | `mcp_servers.debugmcp` |

Expand Down Expand Up @@ -68,6 +69,16 @@ GitHub Copilot CLI uses:
}
```

Claude Code uses `type: "http"` and `url` in the top-level `mcpServers.debugmcp`
entry of `~/.claude.json`. Setup preserves unrelated top-level settings, project
settings (including project-scoped MCP servers), and other user-scoped MCP servers.

On extension activation, migration leaves existing Claude Code `http` and
`streamable-http` entries unchanged, including custom URLs and headers, unless
the URL still ends in `/sse`. Entries with `type: "sse"` or a legacy `/sse` URL
are migrated to the current HTTP configuration. Once migrated, subsequent runs
do not rewrite the configuration or report another migration.

Codex uses TOML:
```toml
[mcp_servers.debugmcp]
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.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "debugmcpextension",
"displayName": "DebugMCP — Agentic Debugging for VS Code, Cursor & More",
"description": "Your AI agent debugs for you — right inside VS Code, Cursor & other VS Code-based editors. Let Copilot, Cline, Cursor, Codex & any MCP agent set breakpoints, step through code, and inspect variables live instead of guessing from logs.",
"version": "2.3.5",
"version": "2.3.6",
"publisher": "ozzafar",
"author": {
"name": "Oz Zafar",
Expand Down
181 changes: 181 additions & 0 deletions src/test/agentConfigurationManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Copyright (c) Microsoft Corporation.

import * as vscode from 'vscode';
import * as assert from 'assert';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import {
AgentConfigurationManager,
JsonAgentInfo,
upsertCodexDebugMCPConfig,
upsertJsonDebugMCPConfigFile
} from '../utils/agentConfigurationManager';
Expand All @@ -31,6 +34,184 @@ suite('AgentConfigurationManager JSON configuration', () => {
});
});

suite('AgentConfigurationManager Claude Code configuration', () => {
const serverPort = 4317;
const expectedEntry = { type: 'http', url: `http://localhost:${serverPort}/mcp` };
const unrelatedConfig = {
theme: 'dark',
projects: {
'/sample/project': {
hasTrustDialogAccepted: true,
mcpServers: {
debugmcp: { type: 'http', url: 'http://localhost:9876/mcp' },
projectServer: { command: 'sample', args: ['--project'] }
}
}
}
};
const otherServer = { type: 'http', url: 'https://other.example/mcp' };
const originalWriteFile = fs.promises.writeFile;
const originalShowInformationMessage = vscode.window.showInformationMessage;
let tempDir: string;
let manager: AgentConfigurationManager;
let supportedAgent: JsonAgentInfo;
let agent: JsonAgentInfo;
let writes: number;
let notifications: string[];

setup(async () => {
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'debugmcp-claude-config-'));
manager = new AgentConfigurationManager({ extensionPath: tempDir } as vscode.ExtensionContext, 180, serverPort);
const agents = await manager['getSupportedAgents']();
const claudeCode = agents.find(candidate => candidate.id === 'claude-code');
assert.ok(claudeCode && claudeCode.configFormat === 'json');
supportedAgent = claudeCode;
agent = { ...supportedAgent, configPath: path.join(tempDir, '.claude.json') };
// Exercise the real setup and migration paths using only the temporary config.
manager['getSupportedAgents'] = async () => [agent];
manager['installDebugMCPSkill'] = async () => null;
writes = 0;
notifications = [];
fs.promises.writeFile = async (...args: Parameters<typeof originalWriteFile>) => {
if (args[0] === agent.configPath) {
writes++;
}
return originalWriteFile(...args);
};
vscode.window.showInformationMessage = async (message: string) => {
notifications.push(message);
return undefined;
};
});

teardown(async () => {
fs.promises.writeFile = originalWriteFile;
vscode.window.showInformationMessage = originalShowInformationMessage;
await fs.promises.rm(tempDir, { recursive: true, force: true });
});

test('uses the user-scoped Claude Code configuration location', () => {
assert.strictEqual(supportedAgent.configPath, path.join(os.homedir(), '.claude.json'));
assert.strictEqual(supportedAgent.mcpServerFieldName, 'mcpServers');
});

test('setup generates only the Claude Code HTTP fields using the configured port', async () => {
const result = await manager['addDebugMCPToAgent'](agent);

assert.strictEqual(result.success, true);
assert.deepStrictEqual(JSON.parse(await fs.promises.readFile(agent.configPath, 'utf8')), {
mcpServers: { debugmcp: expectedEntry }
});
assert.strictEqual(writes, 1);
});

for (const hasUserServers of [false, true]) {
test(`setup preserves unrelated settings with ${hasUserServers ? 'existing' : 'no'} user-scoped servers`, async () => {
const existingServers = hasUserServers ? { other: otherServer } : {};
const initial = {
...unrelatedConfig,
...(hasUserServers ? { mcpServers: existingServers } : {})
};
await originalWriteFile(agent.configPath, JSON.stringify(initial), 'utf8');

const result = await manager['addDebugMCPToAgent'](agent);

assert.strictEqual(result.success, true);
assert.deepStrictEqual(JSON.parse(await fs.promises.readFile(agent.configPath, 'utf8')), {
...unrelatedConfig,
mcpServers: { ...existingServers, debugmcp: expectedEntry }
});
assert.strictEqual(writes, 1);
});
}

for (const type of ['http', 'streamable-http']) {
for (const url of [expectedEntry.url, 'https://custom.example:4443/debug/mcp']) {
test(`migration does not rewrite or notify for ${type} at ${url}`, async () => {
const content = JSON.stringify({
...unrelatedConfig,
mcpServers: {
other: otherServer,
debugmcp: { type, url, headers: { 'X-Test': 'synthetic-value' } }
}
});
await originalWriteFile(agent.configPath, content, 'utf8');

for (let run = 0; run < 2; run++) {
await manager.migrateExistingConfigurations();

assert.strictEqual(await fs.promises.readFile(agent.configPath, 'utf8'), content);
assert.strictEqual(writes, 0);
assert.deepStrictEqual(notifications, []);
}
});
}
}

for (const legacyEntry of [
{ type: 'sse', url: 'http://localhost:3001/sse' },
{ type: 'sse', url: 'http://localhost:3001/mcp' },
{ type: 'http', url: 'http://localhost:3001/sse' },
{ type: 'streamable-http', url: 'http://localhost:3001/sse' }
]) {
test(`migrates ${legacyEntry.type} at ${legacyEntry.url} only once and preserves unrelated settings`, async () => {
await originalWriteFile(agent.configPath, JSON.stringify({
...unrelatedConfig,
mcpServers: { other: otherServer, debugmcp: legacyEntry }
}), 'utf8');

await manager.migrateExistingConfigurations();

const migratedContent = await fs.promises.readFile(agent.configPath, 'utf8');
assert.deepStrictEqual(JSON.parse(migratedContent), {
...unrelatedConfig,
mcpServers: { other: otherServer, debugmcp: expectedEntry }
});
assert.strictEqual(writes, 1);
assert.deepStrictEqual(notifications, [
'DebugMCP: Migrated 1 agent configuration(s) to use the new transport protocol.'
]);

await manager.migrateExistingConfigurations();

assert.strictEqual(await fs.promises.readFile(agent.configPath, 'utf8'), migratedContent);
assert.strictEqual(writes, 1);
assert.strictEqual(notifications.length, 1);
});
}

test('still leaves a valid Copilot CLI HTTP entry unchanged', async () => {
agent = { ...agent, id: 'copilot-cli' };
const content = JSON.stringify({ mcpServers: { debugmcp: { ...expectedEntry, tools: ['*'] } } });
await originalWriteFile(agent.configPath, content, 'utf8');

await manager.migrateExistingConfigurations();

assert.strictEqual(await fs.promises.readFile(agent.configPath, 'utf8'), content);
assert.strictEqual(writes, 0);
assert.deepStrictEqual(notifications, []);
});

test('still migrates a Cline HTTP entry and preserves custom autoApprove settings', async () => {
agent = { ...agent, id: 'cline' };
const autoApprove = ['list_breakpoints'];
await originalWriteFile(agent.configPath, JSON.stringify({
mcpServers: { debugmcp: { ...expectedEntry, autoApprove } }
}), 'utf8');

await manager.migrateExistingConfigurations();
await manager.migrateExistingConfigurations();

assert.deepStrictEqual(JSON.parse(await fs.promises.readFile(agent.configPath, 'utf8')), {
mcpServers: {
debugmcp: { ...expectedEntry, type: 'streamableHttp', autoApprove, disabled: false, timeout: 180 }
}
});
assert.strictEqual(writes, 1);
assert.strictEqual(notifications.length, 1);
});
});

suite('AgentConfigurationManager Codex TOML configuration', () => {
const mcpServerUrl = 'http://localhost:3001/mcp';

Expand Down
27 changes: 25 additions & 2 deletions src/utils/agentConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,18 @@ export class AgentConfigurationManager {
configFormat: 'json',
mcpServerFieldName: 'mcpServers'
},
{
id: 'claude-code',
name: 'claude-code',
displayName: 'Claude Code',
// User-scope MCP servers live under the top-level `mcpServers` field of
// ~/.claude.json (shared across projects), distinct from the per-project
// `projects.<path>.mcpServers` entries Claude Code also stores there.
// See https://code.claude.com/docs/en/mcp.
configPath: path.join(os.homedir(), '.claude.json'),
configFormat: 'json',
mcpServerFieldName: 'mcpServers'
},
{
id: 'codex',
name: 'codex',
Expand All @@ -371,6 +383,17 @@ export class AgentConfigurationManager {
};
}

if (agent?.id === 'claude-code') {
// Claude Code's mcpServers schema only recognizes `type`/`url`/`headers`;
// `streamableHttp` (the shape the other JSON agents below use) isn't one of
// its accepted type values. Its docs treat `http` and `streamable-http` as
// aliases for the same Streamable HTTP transport DebugMCP speaks.
return {
type: 'http',
url: this.getMCPServerUrl()
};
}

return {
autoApprove: [],
disabled: false,
Expand Down Expand Up @@ -436,11 +459,11 @@ export class AgentConfigurationManager {
continue; // DebugMCP not configured for this agent
}

// Check if it's using the old SSE configuration
// Claude Code's HTTP transport is current; still migrate legacy SSE endpoints.
const needsMigration = agent.id === 'copilot-cli'
? debugmcpConfig.type !== 'http' || (debugmcpConfig.url && debugmcpConfig.url.endsWith('/sse'))
: debugmcpConfig.type === 'sse' ||
debugmcpConfig.type === 'http' ||
(debugmcpConfig.type === 'http' && agent.id !== 'claude-code') ||
(debugmcpConfig.url && debugmcpConfig.url.endsWith('/sse'));

if (needsMigration) {
Expand Down
Loading