diff --git a/README.md b/README.md
index 84b869d..11dd867 100644
--- a/README.md
+++ b/README.md
@@ -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)
`workingDirectory` (optional)
`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)
`line` (required) | Set breakpoint at line 25 of main.py |
-| **remove_breakpoint** | Remove a breakpoint from a specific line | `filePath` (required)
`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)
`workingDirectory` (required)
`testName` (optional)
`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)
`lineContent` (required) |
+| **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 |
+| **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
@@ -120,14 +123,16 @@ 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"
}
}
@@ -135,30 +140,30 @@ Add to your Cline settings or `cline_mcp_settings.json`:
```
#### 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"
}
}
}
diff --git a/docs/architecture/debugMCPServer.md b/docs/architecture/debugMCPServer.md
index 370b632..6288486 100644
--- a/docs/architecture/debugMCPServer.md
+++ b/docs/architecture/debugMCPServer.md
@@ -6,7 +6,7 @@ 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
@@ -14,17 +14,17 @@ AI coding agents need a standardized way to control debuggers programmatically.
- 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
@@ -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
@@ -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 |
diff --git a/package-lock.json b/package-lock.json
index 4e56587..86b841b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "debugmcpextension",
- "version": "1.0.6",
+ "version": "1.0.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "debugmcpextension",
- "version": "1.0.6",
+ "version": "1.0.7",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",
diff --git a/package.json b/package.json
index 51f92b1..971386b 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/debugMCPServer.ts b/src/debugMCPServer.ts
index f44f641..9e2369c 100644
--- a/src/debugMCPServer.ts
+++ b/src/debugMCPServer.ts
@@ -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.
@@ -25,7 +25,7 @@ export class DebugMCPServer {
private port: number;
private initialized: boolean = false;
private debuggingHandler: IDebuggingHandler;
- private transports: Map = new Map();
+ private transports: Map = new Map();
constructor(port: number, timeoutInSeconds: number) {
// Initialize the debugging components with dependency injection
@@ -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' +
@@ -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 }] };
});
@@ -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
@@ -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
diff --git a/src/debugState.ts b/src/debugState.ts
index eed9160..6fe8c3a 100644
--- a/src/debugState.ts
+++ b/src/debugState.ts
@@ -1,5 +1,15 @@
// Copyright (c) Microsoft Corporation.
+/**
+ * Represents a single stack frame in the call stack
+ */
+export interface StackFrame {
+ name: string;
+ source?: string;
+ line?: number;
+ column?: number;
+}
+
/**
* Represents the current state of a debugging session
*/
@@ -13,7 +23,9 @@ export class DebugState {
public frameId: number | null;
public threadId: number | null;
public frameName: string | null;
- // TODO breakpoints
+ public stackTrace: StackFrame[];
+ public configurationName: string | null;
+ public breakpoints: string[];
constructor() {
this.sessionActive = false;
@@ -25,6 +37,9 @@ export class DebugState {
this.frameId = null;
this.threadId = null;
this.frameName = null;
+ this.stackTrace = [];
+ this.configurationName = null;
+ this.breakpoints = [];
}
/**
@@ -40,6 +55,9 @@ export class DebugState {
this.frameId = null;
this.threadId = null;
this.frameName = null;
+ this.stackTrace = [];
+ this.configurationName = null;
+ this.breakpoints = [];
}
/**
@@ -91,6 +109,13 @@ export class DebugState {
this.frameName = frameName;
}
+ /**
+ * Update stack trace
+ */
+ public updateStackTrace(stackTrace: StackFrame[]): void {
+ this.stackTrace = [...stackTrace];
+ }
+
/**
* Check if frame name is available
*/
@@ -99,8 +124,19 @@ export class DebugState {
}
/**
- * Clone the current state
+ * Update the configuration name
+ */
+ public updateConfigurationName(configurationName: string | null): void {
+ this.configurationName = configurationName;
+ }
+
+ /**
+ * Update breakpoints list (formatted as "fileName:line" strings)
*/
+ public updateBreakpoints(breakpoints: string[]): void {
+ this.breakpoints = [...breakpoints];
+ }
+
public clone(): DebugState {
const cloned = new DebugState();
cloned.sessionActive = this.sessionActive;
@@ -112,6 +148,53 @@ export class DebugState {
cloned.frameId = this.frameId;
cloned.threadId = this.threadId;
cloned.frameName = this.frameName;
+ cloned.stackTrace = [...this.stackTrace];
+ cloned.configurationName = this.configurationName;
+ cloned.breakpoints = [...this.breakpoints];
return cloned;
}
+
+ /**
+ * Format debug state as a JSON string for structured output
+ */
+ public toString(): string {
+ const stateObject: {
+ sessionActive: boolean;
+ configurationName?: string | null;
+ stackTrace?: string[];
+ breakpoints?: string[];
+ fileFullPath?: string | null;
+ fileName?: string | null;
+ currentLine?: number | null;
+ currentLineContent?: string | null;
+ nextLines?: string[];
+ frameId?: number | null;
+ threadId?: number | null;
+ frameName?: string | null;
+ } = {
+ sessionActive: this.sessionActive,
+ };
+
+ if (this.sessionActive) {
+ stateObject.configurationName = this.configurationName;
+
+ // Compact stack trace: "functionName:line" format
+ stateObject.stackTrace = this.stackTrace.map(frame =>
+ `${frame.name}:${frame.line || '?'}`
+ );
+
+ stateObject.breakpoints = this.breakpoints;
+
+ stateObject.fileFullPath = this.fileFullPath;
+ stateObject.fileName = this.fileName;
+ stateObject.currentLine = this.currentLine;
+ stateObject.currentLineContent = this.currentLineContent;
+ stateObject.nextLines = this.nextLines;
+ stateObject.frameId = this.frameId;
+ stateObject.threadId = this.threadId;
+ stateObject.frameName = this.frameName;
+ }
+
+ return JSON.stringify(stateObject, null, 2);
+ }
}
diff --git a/src/debuggingExecutor.ts b/src/debuggingExecutor.ts
index 4274abb..47d1526 100644
--- a/src/debuggingExecutor.ts
+++ b/src/debuggingExecutor.ts
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
import * as vscode from 'vscode';
-import { DebugState } from './debugState';
+import { DebugState, StackFrame } from './debugState';
/**
* Interface for debugging execution operations
@@ -166,6 +166,7 @@ export class DebuggingExecutor implements IDebuggingExecutor {
const activeSession = vscode.debug.activeDebugSession;
if (activeSession) {
state.sessionActive = true;
+ state.updateConfigurationName(activeSession.configuration.name ?? null);
const activeStackItem = vscode.debug.activeStackItem;
if (activeStackItem && 'frameId' in activeStackItem) {
@@ -181,12 +182,16 @@ export class DebuggingExecutor implements IDebuggingExecutor {
const currentLine = activeEditor.selection.active.line + 1; // 1-based line number
const currentLineContent = activeEditor.document.lineAt(activeEditor.selection.active.line).text.trim();
- // Get next lines
+ // Get next non-empty lines
const nextLines = [];
- for (let i = 1; i <= numNextLines; i++) {
- if (activeEditor.selection.active.line + i < activeEditor.document.lineCount) {
- nextLines.push(activeEditor.document.lineAt(activeEditor.selection.active.line + i).text.trim());
+ let lineOffset = 1;
+ while (nextLines.length < numNextLines &&
+ activeEditor.selection.active.line + lineOffset < activeEditor.document.lineCount) {
+ const lineText = activeEditor.document.lineAt(activeEditor.selection.active.line + lineOffset).text.trim();
+ if (lineText.length > 0) {
+ nextLines.push(lineText);
}
+ lineOffset++;
}
state.updateLocation(
@@ -203,29 +208,52 @@ export class DebuggingExecutor implements IDebuggingExecutor {
console.log('Unable to get debug state:', error);
}
+ // Populate breakpoints as compact "fileName:line" strings
+ const breakpoints = vscode.debug.breakpoints;
+ const formattedBreakpoints = breakpoints
+ .filter((bp): bp is vscode.SourceBreakpoint => bp instanceof vscode.SourceBreakpoint)
+ .map(bp => {
+ const fileName = bp.location.uri.fsPath.split(/[/\\]/).pop() || 'unknown';
+ const line = bp.location.range.start.line + 1;
+ return `${fileName}:${line}`;
+ });
+ state.updateBreakpoints(formattedBreakpoints);
+
return state;
}
/**
- * Extract frame name from the current stack frame
+ * Extract frame name and stack trace from the current debug session
*/
private async extractFrameName(session: vscode.DebugSession, frameId: number, state: DebugState): Promise {
try {
- // Get stack trace to extract frame name
+ // Get full stack trace (up to 50 frames)
const stackTraceResponse = await session.customRequest('stackTrace', {
threadId: state.threadId,
startFrame: 0,
- levels: 1
+ levels: 50
});
if (stackTraceResponse?.stackFrames && stackTraceResponse.stackFrames.length > 0) {
+ // Extract frame name from current frame
const currentFrame = stackTraceResponse.stackFrames[0];
state.updateFrameName(currentFrame.name || null);
+
+ // Build stack trace array
+ const stackTrace: StackFrame[] = stackTraceResponse.stackFrames.map((frame: any) => ({
+ name: frame.name || 'unknown',
+ source: frame.source?.path || frame.source?.name || undefined,
+ line: frame.line || undefined,
+ column: frame.column || undefined,
+ }));
+
+ state.updateStackTrace(stackTrace);
}
} catch (error) {
- console.log('Unable to extract frame name:', error);
- // Set empty frame name on error
+ console.log('Unable to extract stack info:', error);
+ // Set empty values on error
state.updateFrameName(null);
+ state.updateStackTrace([]);
}
}
diff --git a/src/debuggingHandler.ts b/src/debuggingHandler.ts
index d38444a..8dedcb8 100644
--- a/src/debuggingHandler.ts
+++ b/src/debuggingHandler.ts
@@ -10,7 +10,7 @@ import { logger } from './utils/logger';
* Interface for debugging handler operations
*/
export interface IDebuggingHandler {
- handleStartDebugging(args: { fileFullPath: string; workingDirectory: string; testName?: string }): Promise;
+ handleStartDebugging(args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string }): Promise;
handleStopDebugging(): Promise;
handleStepOver(): Promise;
handleStepInto(): Promise;
@@ -48,11 +48,12 @@ export class DebuggingHandler implements IDebuggingHandler {
fileFullPath: string;
workingDirectory: string;
testName?: string;
+ configurationName?: string;
}): Promise {
- const { fileFullPath, workingDirectory, testName } = args;
+ const { fileFullPath, workingDirectory, testName, configurationName } = args;
try {
- let selectedConfigName = await this.configManager.promptForConfiguration(workingDirectory);
+ let selectedConfigName = configurationName ?? await this.configManager.promptForConfiguration(workingDirectory);
// Get debug configuration from launch.json or create default
const debugConfig = await this.configManager.getDebugConfig(
@@ -75,7 +76,7 @@ export class DebuggingHandler implements IDebuggingHandler {
const configInfo = selectedConfigName ? ` using configuration '${selectedConfigName}'` : ' with default configuration';
const testInfo = testName ? ` (test: ${testName})` : '';
const currentState = await this.executor.getCurrentDebugState(this.numNextLines);
- return `Debug session started successfully for: ${fileFullPath}${configInfo}${testInfo}. Current state: ${this.formatDebugState(currentState)}`;
+ return `Debug session started successfully for: ${fileFullPath}${configInfo}${testInfo}. Current state: ${currentState.toString()}`;
} else {
throw new Error('Failed to start debug session. Make sure the appropriate language extension is installed.');
}
@@ -137,8 +138,7 @@ export class DebuggingHandler implements IDebuggingHandler {
// Wait for debugger state to change
const afterState = await this.waitForStateChange(beforeState);
- // Format the debug state as a string
- return this.formatDebugState(afterState);
+ return afterState.toString();
} catch (error) {
throw new Error(`Error executing step over: ${error}`);
}
@@ -161,8 +161,7 @@ export class DebuggingHandler implements IDebuggingHandler {
// Wait for debugger state to change
const afterState = await this.waitForStateChange(beforeState);
- // Format the debug state as a string
- return this.formatDebugState(afterState);
+ return afterState.toString();
} catch (error) {
throw new Error(`Error executing step into: ${error}`);
}
@@ -185,8 +184,7 @@ export class DebuggingHandler implements IDebuggingHandler {
// Wait for debugger state to change
const afterState = await this.waitForStateChange(beforeState);
- // Format the debug state as a string
- return this.formatDebugState(afterState);
+ return afterState.toString();
} catch (error) {
throw new Error(`Error executing step out: ${error}`);
}
@@ -209,10 +207,7 @@ export class DebuggingHandler implements IDebuggingHandler {
// Wait for debugger state to change
const afterState = await this.waitForStateChange(beforeState);
- let result = this.formatDebugState(afterState);
-
-
- return result;
+ return afterState.toString();
} catch (error) {
throw new Error(`Error executing continue: ${error}`);
}
@@ -421,41 +416,6 @@ export class DebuggingHandler implements IDebuggingHandler {
}
}
- /**
- * Format debug state as a readable string
- */
- private formatDebugState(state: DebugState): string {
- if (!state.sessionActive) {
- return 'Debug session is not active';
- }
-
- let output = 'Debug State:\n==========\n\n';
-
- if (state.hasFrameName()) {
- output += `Frame: ${state.frameName}\n`;
- }
-
- if (state.hasLocationInfo()) {
- output += `File: ${state.fileName}\n`;
- output += `Line: ${state.currentLine}\n`;
-
- output += `${state.currentLine}: ${state.currentLineContent}\n`;
-
- // Show next few lines for context
- // if (state.nextLines && state.nextLines.length > 0) {
- // output += '\nNext lines:\n';
- // state.nextLines.forEach((line, index) => {
- // const lineNumber = (state.currentLine || 0) + index + 1;
- // output += ` ${lineNumber}: ${line}\n`;
- // });
- // }
- } else {
- output += 'No location information available. The session might have stopped or ended\n';
- }
-
- return output;
- }
-
/**
* Get current debug state
*/
diff --git a/src/extension.ts b/src/extension.ts
index 4b801a6..70b9c34 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -24,6 +24,13 @@ export async function activate(context: vscode.ExtensionContext) {
// Initialize Agent Configuration Manager
agentConfigManager = new AgentConfigurationManager(context, timeoutInSeconds, serverPort);
+ // Migrate existing SSE configurations to streamableHttp (for backward compatibility)
+ try {
+ await agentConfigManager.migrateExistingConfigurations();
+ } catch (error) {
+ logger.error('Error migrating existing configurations', error);
+ }
+
// Initialize MCP Server
try {
logger.info('Starting MCP server initialization...');
diff --git a/src/utils/agentConfigurationManager.ts b/src/utils/agentConfigurationManager.ts
index c8dec3b..7c0de61 100644
--- a/src/utils/agentConfigurationManager.ts
+++ b/src/utils/agentConfigurationManager.ts
@@ -156,11 +156,81 @@ export class AgentConfigurationManager {
autoApprove: [],
disabled: false,
timeout: this.timeoutInSeconds,
- type: "sse",
- url: `http://localhost:${this.serverPort}/sse`
+ type: "streamableHttp",
+ url: `http://localhost:${this.serverPort}/mcp`
};
}
+ /**
+ * Migrate existing SSE configurations to streamableHttp
+ * This should be called on extension activation to ensure backward compatibility
+ */
+ public async migrateExistingConfigurations(): Promise {
+ const agents = await this.getSupportedAgents();
+ let migrationCount = 0;
+
+ for (const agent of agents) {
+ try {
+ if (!fs.existsSync(agent.configPath)) {
+ continue;
+ }
+
+ const configContent = await fs.promises.readFile(agent.configPath, 'utf8');
+ let config: any;
+
+ try {
+ config = JSON.parse(configContent);
+ } catch {
+ continue; // Skip if config can't be parsed
+ }
+
+ const fieldName = agent.mcpServerFieldName;
+ const debugmcpConfig = config[fieldName]?.debugmcp;
+
+ if (!debugmcpConfig) {
+ continue; // DebugMCP not configured for this agent
+ }
+
+ // Check if it's using the old SSE configuration
+ const needsMigration =
+ debugmcpConfig.type === 'sse' ||
+ debugmcpConfig.type === 'http' ||
+ (debugmcpConfig.url && debugmcpConfig.url.endsWith('/sse'));
+
+ if (needsMigration) {
+ console.log(`Migrating DebugMCP configuration for ${agent.displayName} from SSE to streamableHttp`);
+
+ // Update to new configuration
+ config[fieldName].debugmcp = this.getDebugMCPConfig();
+
+ // Preserve any custom autoApprove settings
+ if (debugmcpConfig.autoApprove && Array.isArray(debugmcpConfig.autoApprove)) {
+ config[fieldName].debugmcp.autoApprove = debugmcpConfig.autoApprove;
+ }
+
+ // Write the migrated config
+ await fs.promises.writeFile(
+ agent.configPath,
+ JSON.stringify(config, null, 2),
+ 'utf8'
+ );
+
+ migrationCount++;
+ console.log(`Successfully migrated ${agent.displayName} configuration`);
+ }
+ } catch (error) {
+ console.error(`Error migrating config for ${agent.name}:`, error);
+ // Continue with other agents even if one fails
+ }
+ }
+
+ if (migrationCount > 0) {
+ vscode.window.showInformationMessage(
+ `DebugMCP: Migrated ${migrationCount} agent configuration(s) to use the new transport protocol.`
+ );
+ }
+ }
+
/**
* Add DebugMCP server configuration to the specified agent's config
*/