Skip to content

Commit 843d77a

Browse files
authored
Merge pull request #33 from microsoft/ozzafar/improved_response_state
Migrate SSE to StreamableHttp
2 parents e021997 + 150612f commit 843d77a

10 files changed

Lines changed: 314 additions & 139 deletions

README.md

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,24 @@ DebugMCP bridges the gap between professional debugging and AI-assisted developm
3232

3333
### 🔧 Tools
3434

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

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

121124
### Manual MCP Server Registration (Optional)
122125

126+
> **🔄 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.
127+
123128
#### Cline
124129
Add to your Cline settings or `cline_mcp_settings.json`:
125130
```json
126131
{
127132
"mcpServers": {
128133
"debugmcp": {
129-
"transport": "sse",
130-
"url": "http://localhost:3001/sse",
134+
"type": "streamableHttp",
135+
"url": "http://localhost:3001/mcp",
131136
"description": "DebugMCP - AI-powered debugging assistant"
132137
}
133138
}
134139
}
135140
```
136141

137142
#### GitHub Copilot
138-
Add to your Copilot workspace settings (`.vscode/settings.json`):
143+
Add to your VS Code settings (`settings.json`):
139144
```json
140145
{
141-
"github.copilot.mcp.servers": {
142-
"debugmcp": {
143-
"type": "sse",
144-
"url": "http://localhost:3001/sse",
145-
"description": "DebugMCP - Multi-language debugging support"
146+
"mcp": {
147+
"servers": {
148+
"debugmcp": {
149+
"type": "http",
150+
"url": "http://localhost:3001/mcp",
151+
"description": "DebugMCP - Multi-language debugging support"
152+
}
146153
}
147154
}
148155
}
149156
```
150157

151-
#### Roo Code
152-
Add to Roo's MCP settings:
158+
#### Cursor
159+
Add to Cursor's MCP settings:
153160
```json
154161
{
155-
"mcp": {
156-
"servers": {
157-
"debugmcp": {
158-
"type": "sse",
159-
"url": "http://localhost:3001/sse",
160-
"description": "DebugMCP - Debugging tools for AI assistants"
161-
}
162+
"mcpServers": {
163+
"debugmcp": {
164+
"type": "streamableHttp",
165+
"url": "http://localhost:3001/mcp",
166+
"description": "DebugMCP - Debugging tools for AI assistants"
162167
}
163168
}
164169
}

docs/architecture/debugMCPServer.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@ The MCP server component that exposes VS Code debugging capabilities to AI agent
66

77
## Motivation
88

9-
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.
9+
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.
1010

1111
## Responsibility
1212

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

1919
## Architecture Position
2020

2121
```
2222
AI Agent (MCP Client)
2323
24-
SSE Connection (GET /sse + POST /messages)
24+
HTTP POST /mcp
2525
┌───────────────────┐
2626
│ DebugMCPServer │ ◄── You are here
27-
(express + SSE) │
27+
│ (express + HTTP) │
2828
└───────────────────┘
2929
3030
▼ Delegates to
@@ -38,15 +38,14 @@ AI Agent (MCP Client)
3838
### Tools vs Resources
3939

4040
- **Tools**: Actions the AI can perform (start debugging, step over, etc.)
41-
- **Resources**: Documentation the AI can read for guidance
41+
- **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)
4242

43-
### SSE Transport
43+
### Streamable HTTP Transport
4444

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

49-
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.
48+
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.
5049

5150
## Key Code Locations
5251

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

6059
| Tool | Description |
6160
|------|-------------|
61+
| `get_debug_instructions` | Get debugging guide (for clients that don't support resources) |
6262
| `start_debugging` | Start a debug session |
6363
| `stop_debugging` | Stop current session |
6464
| `step_over/into/out` | Stepping commands |

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "debugmcpextension",
33
"displayName": "DebugMCP",
44
"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.",
5-
"version": "1.0.6",
5+
"version": "1.0.7",
66
"publisher": "ozzafar",
77
"author": {
88
"name": "Oz Zafar",

src/debugMCPServer.ts

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '.';
1414
import { logger } from './utils/logger';
1515
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
16-
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
16+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
1717

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

3030
constructor(port: number, timeoutInSeconds: number) {
3131
// Initialize the debugging components with dependency injection
@@ -57,6 +57,16 @@ export class DebugMCPServer {
5757
* Setup MCP tools that delegate to the debugging handler
5858
*/
5959
private setupTools() {
60+
// Get debug instructions tool (for clients that don't support MCP resources like GitHub Copilot)
61+
this.mcpServer!.registerTool('get_debug_instructions', {
62+
description: 'Get the debugging guide with step-by-step instructions for effective debugging. ' +
63+
'Returns comprehensive guidance including breakpoint strategies, root cause analysis framework, ' +
64+
'and best practices. Call this before starting a debug session.',
65+
}, async () => {
66+
const content = await this.loadMarkdownFile('agent-resources/debug_instructions.md');
67+
return { content: [{ type: 'text' as const, text: content }] };
68+
});
69+
6070
// Start debugging tool
6171
this.mcpServer!.registerTool('start_debugging', {
6272
description: 'IMPORTANT DEBUGGING TOOL - Start a debug session for a code file' +
@@ -67,13 +77,21 @@ export class DebugMCPServer {
6777
'\n• Functions return incorrect results' +
6878
'\n• Code behaves differently than expected' +
6979
'\n• User reports "it doesn\'t work"' +
70-
'\n\n⚠️ CRITICAL: Before using this tool, first read debugmcp://docs/debug_instructions resource!',
80+
'\n\n⚠️ CRITICAL: Before using this tool, first call get_debug_instructions or read debugmcp://docs/debug_instructions resource!',
7181
inputSchema: {
7282
fileFullPath: z.string().describe('Full path to the source code file to debug'),
7383
workingDirectory: z.string().describe('Working directory for the debug session'),
74-
testName: z.string().optional().describe('Name of the specific test name to debug.'),
84+
testName: z.string().optional().describe(
85+
'Name of a specific test name to debug. ' +
86+
'Only provide this when debugging a single test method. ' +
87+
'Leave empty to debug the entire file or test class.'
88+
),
89+
configurationName: z.string().optional().describe(
90+
'Name of a specific debug configuration from launch.json to use. ' +
91+
'Leave empty to be prompted to select a configuration interactively.'
92+
),
7593
},
76-
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string }) => {
94+
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string }) => {
7795
const result = await this.debuggingHandler.handleStartDebugging(args);
7896
return { content: [{ type: 'text' as const, text: result }] };
7997
});
@@ -308,29 +326,39 @@ export class DebugMCPServer {
308326
const express = expressModule.default;
309327
const app = express();
310328

311-
// SSE endpoint — clients connect here to establish the MCP session
312-
app.get('/sse', async (req: any, res: any) => {
313-
logger.info('New SSE connection established');
314-
const transport = new SSEServerTransport('/messages', res);
315-
this.transports.set(transport.sessionId, transport);
329+
// Parse JSON body for incoming requests
330+
app.use(express.json());
331+
332+
// Streamable HTTP endpoint — handles MCP protocol messages
333+
app.post('/mcp', async (req: any, res: any) => {
334+
logger.info('New MCP request received');
335+
336+
// Create a new transport for each request (stateless mode)
337+
const transport = new StreamableHTTPServerTransport({
338+
sessionIdGenerator: undefined, // Stateless mode - no session management
339+
});
316340

317-
transport.onclose = () => {
318-
this.transports.delete(transport.sessionId);
319-
logger.info(`SSE transport closed: ${transport.sessionId}`);
320-
};
341+
// Clean up transport when response closes
342+
res.on('close', () => {
343+
transport.close();
344+
logger.info('MCP transport closed');
345+
});
321346

347+
// Connect the MCP server to this transport
322348
await this.mcpServer!.connect(transport);
349+
350+
// Handle the incoming request
351+
await transport.handleRequest(req, res, req.body);
323352
});
324353

325-
// Message endpoint — clients POST JSON-RPC messages here
326-
app.post('/messages', async (req: any, res: any) => {
327-
const sessionId = req.query.sessionId as string;
328-
const transport = this.transports.get(sessionId);
329-
if (!transport) {
330-
res.status(404).json({ error: 'Session not found' });
331-
return;
332-
}
333-
await transport.handlePostMessage(req, res);
354+
// Legacy SSE endpoint for backward compatibility
355+
// Redirects to the new /mcp endpoint with appropriate headers
356+
app.get('/sse', async (req: any, res: any) => {
357+
res.status(410).json({
358+
error: 'SSE endpoint deprecated',
359+
message: 'Please use POST /mcp endpoint instead',
360+
newEndpoint: '/mcp'
361+
});
334362
});
335363

336364
// Start HTTP server
@@ -353,14 +381,8 @@ export class DebugMCPServer {
353381
* Stop the MCP server
354382
*/
355383
async stop() {
356-
// Close all active transports
357-
for (const [sessionId, transport] of this.transports) {
358-
try {
359-
await transport.close();
360-
} catch (error) {
361-
logger.error(`Error closing transport ${sessionId}`, error);
362-
}
363-
}
384+
// Note: With stateless StreamableHTTPServerTransport, transports are closed per-request
385+
// No need to track and close them manually
364386
this.transports.clear();
365387

366388
// Close the HTTP server

0 commit comments

Comments
 (0)