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
69 changes: 37 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,24 @@ DebugMCP bridges the gap between professional debugging and AI-assisted developm

### 🔧 Tools

| Tool | Description | Parameters | Example Usage |
|------|-------------|------------|---------------|
| **start_debugging** | Start a debug session for a source code file | `filePath` (required)<br>`workingDirectory` (optional)<br>`configurationName` (optional) | Start debugging a Python script |
| **stop_debugging** | Stop the current debug session | None | End the current debugging session |
| **step_over** | Execute the next line (step over function calls) | None | Move to next line without entering functions |
| **step_into** | Step into function calls | None | Enter function calls to debug them |
| **step_out** | Step out of the current function | None | Exit current function and return to caller |
| **continue_execution** | Continue until next breakpoint | None | Resume execution until hitting a breakpoint |
| **restart_debugging** | Restart the current debug session | None | Restart debugging from the beginning |
| **add_breakpoint** | Add a breakpoint at a specific line | `filePath` (required)<br>`line` (required) | Set breakpoint at line 25 of main.py |
| **remove_breakpoint** | Remove a breakpoint from a specific line | `filePath` (required)<br>`line` (required) | Remove breakpoint from line 25 |
| **list_breakpoints** | List all active breakpoints | None | Show all currently set breakpoints |
| **get_debug_status** | Get current debug session status | None | Check if debugging session is active |
| **get_variables** | Get variables and their values | `scope` (optional: 'local', 'global', 'all') | Inspect local variables |
| **evaluate_expression** | Evaluate an expression in debug context | `expression` (required) | Evaluate `user.name` or `len(items)` |
| Tool | Description | Parameters |
|------|-------------|------------|
| **get_debug_instructions** | Get the debugging guide with best practices and workflow instructions | None |
| **start_debugging** | Start a debug session for a source code file | `fileFullPath` (required)<br>`workingDirectory` (required)<br>`testName` (optional)<br>`configurationName` (optional) |
| **stop_debugging** | Stop the current debug session | None |
| **step_over** | Execute the next line (step over function calls) | None |
| **step_into** | Step into function calls | None |
| **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) |
| **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 |
| **get_variables_values** | Get variables and their values at current execution point | `scope` (optional: 'local', 'global', 'all') |
| **evaluate_expression** | Evaluate an expression in debug context | `expression` (required) |

> **Note:** The `get_debug_instructions` tool is particularly useful for AI clients like GitHub Copilot that don't support MCP resources. It provides the same debugging guide content that is also available as an MCP resource.

### 🎯 Debugging Best Practices

Expand Down Expand Up @@ -120,45 +123,47 @@ The extension runs an MCP server automatically. It will pop up a message to auto

### Manual MCP Server Registration (Optional)

> **🔄 Auto-Migration**: If you previously configured DebugMCP with SSE transport, the extension will automatically migrate your configuration to the new Streamable HTTP transport on activation.

#### Cline
Add to your Cline settings or `cline_mcp_settings.json`:
```json
{
"mcpServers": {
"debugmcp": {
"transport": "sse",
"url": "http://localhost:3001/sse",
"type": "streamableHttp",
"url": "http://localhost:3001/mcp",
"description": "DebugMCP - AI-powered debugging assistant"
}
}
}
```

#### GitHub Copilot
Add to your Copilot workspace settings (`.vscode/settings.json`):
Add to your VS Code settings (`settings.json`):
```json
{
"github.copilot.mcp.servers": {
"debugmcp": {
"type": "sse",
"url": "http://localhost:3001/sse",
"description": "DebugMCP - Multi-language debugging support"
"mcp": {
"servers": {
"debugmcp": {
"type": "http",
"url": "http://localhost:3001/mcp",
"description": "DebugMCP - Multi-language debugging support"
}
}
}
}
```

#### Roo Code
Add to Roo's MCP settings:
#### Cursor
Add to Cursor's MCP settings:
```json
{
"mcp": {
"servers": {
"debugmcp": {
"type": "sse",
"url": "http://localhost:3001/sse",
"description": "DebugMCP - Debugging tools for AI assistants"
}
"mcpServers": {
"debugmcp": {
"type": "streamableHttp",
"url": "http://localhost:3001/mcp",
"description": "DebugMCP - Debugging tools for AI assistants"
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions docs/architecture/debugMCPServer.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ The MCP server component that exposes VS Code debugging capabilities to AI agent

## Motivation

AI coding agents need a standardized way to control debuggers programmatically. MCP provides this standard, and `DebugMCPServer` implements it using the official `@modelcontextprotocol/sdk` with Server-Sent Events (SSE) transport over an express HTTP server for real-time bidirectional communication.
AI coding agents need a standardized way to control debuggers programmatically. MCP provides this standard, and `DebugMCPServer` implements it using the official `@modelcontextprotocol/sdk` with Streamable HTTP transport over an express HTTP server.

## Responsibility

- Initialize and manage the MCP server lifecycle (using `McpServer` from `@modelcontextprotocol/sdk`)
- Register debugging tools that AI agents can invoke
- Register documentation resources for agent guidance
- Delegate all debugging operations to `DebuggingHandler`
- Manage SSE transport connections via `SSEServerTransport` on configurable port (default: 3001)
- Manage Streamable HTTP transport via `StreamableHTTPServerTransport` on configurable port (default: 3001)

## Architecture Position

```
AI Agent (MCP Client)
SSE Connection (GET /sse + POST /messages)
HTTP POST /mcp
┌───────────────────┐
│ DebugMCPServer │ ◄── You are here
(express + SSE) │
│ (express + HTTP) │
└───────────────────┘
▼ Delegates to
Expand All @@ -38,15 +38,14 @@ AI Agent (MCP Client)
### Tools vs Resources

- **Tools**: Actions the AI can perform (start debugging, step over, etc.)
- **Resources**: Documentation the AI can read for guidance
- **Resources**: Documentation the AI can read for guidance (note: some clients like GitHub Copilot don't support resources, so the `get_debug_instructions` tool is also provided)

### SSE Transport
### Streamable HTTP Transport

Uses HTTP with Server-Sent Events for persistent connections. The express server exposes two endpoints:
- `GET /sse` — Establishes the SSE stream and creates a session
- `POST /messages?sessionId=...` — Receives JSON-RPC messages from the client
Uses stateless HTTP POST requests for MCP communication. The express server exposes:
- `POST /mcp` — Handles all MCP protocol messages (JSON-RPC over HTTP)

Each client connection creates an `SSEServerTransport` instance that is connected to the `McpServer`. The server tracks active transports by session ID and cleans them up on disconnect.
Each request creates a new `StreamableHTTPServerTransport` instance in stateless mode, which is cleaned up when the response closes. This approach is simpler than session-based transports and works well with standard HTTP clients.

## Key Code Locations

Expand All @@ -59,6 +58,7 @@ Each client connection creates an `SSEServerTransport` instance that is connecte

| Tool | Description |
|------|-------------|
| `get_debug_instructions` | Get debugging guide (for clients that don't support resources) |
| `start_debugging` | Start a debug session |
| `stop_debugging` | Stop current session |
| `step_over/into/out` | Stepping commands |
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",
"description": "A VSCode extension that provides comprehensive multi-language debugging capabilities and automatically exposes itself as an MCP (Model Context Protocol) server for seamless integration with AI assistants.",
"version": "1.0.6",
"version": "1.0.7",
"publisher": "ozzafar",
"author": {
"name": "Oz Zafar",
Expand Down
84 changes: 53 additions & 31 deletions src/debugMCPServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '.';
import { logger } from './utils/logger';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

/**
* Main MCP server class that exposes debugging functionality as tools and resources.
Expand All @@ -25,7 +25,7 @@ export class DebugMCPServer {
private port: number;
private initialized: boolean = false;
private debuggingHandler: IDebuggingHandler;
private transports: Map<string, SSEServerTransport> = new Map();
private transports: Map<string, StreamableHTTPServerTransport> = new Map();

constructor(port: number, timeoutInSeconds: number) {
// Initialize the debugging components with dependency injection
Expand Down Expand Up @@ -57,6 +57,16 @@ export class DebugMCPServer {
* Setup MCP tools that delegate to the debugging handler
*/
private setupTools() {
// Get debug instructions tool (for clients that don't support MCP resources like GitHub Copilot)
this.mcpServer!.registerTool('get_debug_instructions', {
description: 'Get the debugging guide with step-by-step instructions for effective debugging. ' +
'Returns comprehensive guidance including breakpoint strategies, root cause analysis framework, ' +
'and best practices. Call this before starting a debug session.',
}, async () => {
const content = await this.loadMarkdownFile('agent-resources/debug_instructions.md');
return { content: [{ type: 'text' as const, text: content }] };
});

// Start debugging tool
this.mcpServer!.registerTool('start_debugging', {
description: 'IMPORTANT DEBUGGING TOOL - Start a debug session for a code file' +
Expand All @@ -67,13 +77,21 @@ export class DebugMCPServer {
'\n• Functions return incorrect results' +
'\n• Code behaves differently than expected' +
'\n• User reports "it doesn\'t work"' +
'\n\n⚠️ CRITICAL: Before using this tool, first read debugmcp://docs/debug_instructions resource!',
'\n\n⚠️ CRITICAL: Before using this tool, first call get_debug_instructions or read debugmcp://docs/debug_instructions resource!',
inputSchema: {
fileFullPath: z.string().describe('Full path to the source code file to debug'),
workingDirectory: z.string().describe('Working directory for the debug session'),
testName: z.string().optional().describe('Name of the specific test name to debug.'),
testName: z.string().optional().describe(
'Name of a specific test name to debug. ' +
'Only provide this when debugging a single test method. ' +
'Leave empty to debug the entire file or test class.'
),
configurationName: z.string().optional().describe(
'Name of a specific debug configuration from launch.json to use. ' +
'Leave empty to be prompted to select a configuration interactively.'
),
},
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string }) => {
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string }) => {
const result = await this.debuggingHandler.handleStartDebugging(args);
return { content: [{ type: 'text' as const, text: result }] };
});
Expand Down Expand Up @@ -308,29 +326,39 @@ export class DebugMCPServer {
const express = expressModule.default;
const app = express();

// SSE endpoint — clients connect here to establish the MCP session
app.get('/sse', async (req: any, res: any) => {
logger.info('New SSE connection established');
const transport = new SSEServerTransport('/messages', res);
this.transports.set(transport.sessionId, transport);
// Parse JSON body for incoming requests
app.use(express.json());

// Streamable HTTP endpoint — handles MCP protocol messages
app.post('/mcp', async (req: any, res: any) => {
logger.info('New MCP request received');

// Create a new transport for each request (stateless mode)
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // Stateless mode - no session management
});

transport.onclose = () => {
this.transports.delete(transport.sessionId);
logger.info(`SSE transport closed: ${transport.sessionId}`);
};
// Clean up transport when response closes
res.on('close', () => {
transport.close();
logger.info('MCP transport closed');
});

// Connect the MCP server to this transport
await this.mcpServer!.connect(transport);

// Handle the incoming request
await transport.handleRequest(req, res, req.body);
});

// Message endpoint — clients POST JSON-RPC messages here
app.post('/messages', async (req: any, res: any) => {
const sessionId = req.query.sessionId as string;
const transport = this.transports.get(sessionId);
if (!transport) {
res.status(404).json({ error: 'Session not found' });
return;
}
await transport.handlePostMessage(req, res);
// Legacy SSE endpoint for backward compatibility
// Redirects to the new /mcp endpoint with appropriate headers
app.get('/sse', async (req: any, res: any) => {
res.status(410).json({
error: 'SSE endpoint deprecated',
message: 'Please use POST /mcp endpoint instead',
newEndpoint: '/mcp'
});
});

// Start HTTP server
Expand All @@ -353,14 +381,8 @@ export class DebugMCPServer {
* Stop the MCP server
*/
async stop() {
// Close all active transports
for (const [sessionId, transport] of this.transports) {
try {
await transport.close();
} catch (error) {
logger.error(`Error closing transport ${sessionId}`, error);
}
}
// Note: With stateless StreamableHTTPServerTransport, transports are closed per-request
// No need to track and close them manually
this.transports.clear();

// Close the HTTP server
Expand Down
Loading