Skip to content

Commit 686977c

Browse files
committed
multi mcp client fix
1 parent c5d7493 commit 686977c

1 file changed

Lines changed: 44 additions & 27 deletions

File tree

src/debugMCPServer.ts

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ export function isLoopbackOrigin(originHeader: string | undefined): boolean {
8181
}
8282

8383
export class DebugMCPServer {
84-
private mcpServer: McpServer | null = null;
8584
private httpServer: http.Server | null = null;
8685
private port: number;
8786
private host: string;
@@ -98,29 +97,41 @@ export class DebugMCPServer {
9897
}
9998

10099
/**
101-
* Initialize the MCP server
100+
* Initialize the MCP server factory.
101+
*
102+
* NOTE: We no longer hold a singleton McpServer here. The stateless
103+
* StreamableHTTPServerTransport requires a fresh McpServer per request
104+
* (calling .connect() twice on the same server throws "Already connected
105+
* to a transport"). The /mcp handler builds one on demand via
106+
* createMcpServer().
102107
*/
103108
async initialize() {
104109
if (this.initialized) {
105110
return;
106111
}
112+
this.initialized = true;
113+
}
107114

108-
this.mcpServer = new McpServer({
115+
/**
116+
* Build a fresh McpServer with all tools and resources registered.
117+
* Called once per incoming MCP request.
118+
*/
119+
private createMcpServer(): McpServer {
120+
const server = new McpServer({
109121
name: 'debugmcp',
110122
version: '1.0.0',
111123
});
112-
113-
this.setupTools();
114-
this.setupResources();
115-
this.initialized = true;
124+
this.setupTools(server);
125+
this.setupResources(server);
126+
return server;
116127
}
117128

118129
/**
119130
* Setup MCP tools that delegate to the debugging handler
120131
*/
121-
private setupTools() {
132+
private setupTools(server: McpServer) {
122133
// Get debug instructions tool (for clients that don't support MCP resources like GitHub Copilot)
123-
this.mcpServer!.registerTool('get_debug_instructions', {
134+
server.registerTool('get_debug_instructions', {
124135
description: 'Get the debugging guide with step-by-step instructions for effective debugging. ' +
125136
'Returns comprehensive guidance including breakpoint strategies, root cause analysis framework, ' +
126137
'and best practices. Call this before starting a debug session.',
@@ -130,7 +141,7 @@ export class DebugMCPServer {
130141
});
131142

132143
// Start debugging tool
133-
this.mcpServer!.registerTool('start_debugging', {
144+
server.registerTool('start_debugging', {
134145
description: 'IMPORTANT DEBUGGING TOOL - Start a debug session for a code file' +
135146
'\n\nUSE THIS WHEN:' +
136147
'\n• Any bug, error, or unexpected behavior occurs' +
@@ -159,55 +170,55 @@ export class DebugMCPServer {
159170
});
160171

161172
// Stop debugging tool
162-
this.mcpServer!.registerTool('stop_debugging', {
173+
server.registerTool('stop_debugging', {
163174
description: 'Stop the current debug session',
164175
}, async () => {
165176
const result = await this.debuggingHandler.handleStopDebugging();
166177
return { content: [{ type: 'text' as const, text: result }] };
167178
});
168179

169180
// Step over tool
170-
this.mcpServer!.registerTool('step_over', {
181+
server.registerTool('step_over', {
171182
description: 'Execute the current line of code without diving into it.',
172183
}, async () => {
173184
const result = await this.debuggingHandler.handleStepOver();
174185
return { content: [{ type: 'text' as const, text: result }] };
175186
});
176187

177188
// Step into tool
178-
this.mcpServer!.registerTool('step_into', {
189+
server.registerTool('step_into', {
179190
description: 'Dive into the current line of code.',
180191
}, async () => {
181192
const result = await this.debuggingHandler.handleStepInto();
182193
return { content: [{ type: 'text' as const, text: result }] };
183194
});
184195

185196
// Step out tool
186-
this.mcpServer!.registerTool('step_out', {
197+
server.registerTool('step_out', {
187198
description: 'Step out of the current function',
188199
}, async () => {
189200
const result = await this.debuggingHandler.handleStepOut();
190201
return { content: [{ type: 'text' as const, text: result }] };
191202
});
192203

193204
// Continue execution tool
194-
this.mcpServer!.registerTool('continue_execution', {
205+
server.registerTool('continue_execution', {
195206
description: 'Resume program execution until the next breakpoint is hit or the program completes.',
196207
}, async () => {
197208
const result = await this.debuggingHandler.handleContinue();
198209
return { content: [{ type: 'text' as const, text: result }] };
199210
});
200211

201212
// Restart debugging tool
202-
this.mcpServer!.registerTool('restart_debugging', {
213+
server.registerTool('restart_debugging', {
203214
description: 'Restart the debug session from the beginning with the same configuration.',
204215
}, async () => {
205216
const result = await this.debuggingHandler.handleRestart();
206217
return { content: [{ type: 'text' as const, text: result }] };
207218
});
208219

209220
// Add breakpoint tool
210-
this.mcpServer!.registerTool('add_breakpoint', {
221+
server.registerTool('add_breakpoint', {
211222
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.',
212223
inputSchema: {
213224
fileFullPath: z.string().describe('Full path to the file'),
@@ -219,7 +230,7 @@ export class DebugMCPServer {
219230
});
220231

221232
// Remove breakpoint tool
222-
this.mcpServer!.registerTool('remove_breakpoint', {
233+
server.registerTool('remove_breakpoint', {
223234
description: 'Remove a breakpoint that is no longer needed.',
224235
inputSchema: {
225236
fileFullPath: z.string().describe('Full path to the file'),
@@ -231,23 +242,23 @@ export class DebugMCPServer {
231242
});
232243

233244
// Clear all breakpoints tool
234-
this.mcpServer!.registerTool('clear_all_breakpoints', {
245+
server.registerTool('clear_all_breakpoints', {
235246
description: 'Clear all breakpoints at once. Use this after verifying the root cause to clean up before moving on to the next task.',
236247
}, async () => {
237248
const result = await this.debuggingHandler.handleClearAllBreakpoints();
238249
return { content: [{ type: 'text' as const, text: result }] };
239250
});
240251

241252
// List breakpoints tool
242-
this.mcpServer!.registerTool('list_breakpoints', {
253+
server.registerTool('list_breakpoints', {
243254
description: 'View all currently set breakpoints across all files.',
244255
}, async () => {
245256
const result = await this.debuggingHandler.handleListBreakpoints();
246257
return { content: [{ type: 'text' as const, text: result }] };
247258
});
248259

249260
// Get variables tool
250-
this.mcpServer!.registerTool('get_variables_values', {
261+
server.registerTool('get_variables_values', {
251262
description: 'Inspect all variable values at the current execution point. This is your window into program state - see what data looks like at runtime, verify assumptions, identify unexpected values, and understand why code behaves as it does.',
252263
inputSchema: {
253264
scope: z.enum(['local', 'global', 'all']).optional().describe("Variable scope: 'local', 'global', or 'all'"),
@@ -258,7 +269,7 @@ export class DebugMCPServer {
258269
});
259270

260271
// Evaluate expression tool
261-
this.mcpServer!.registerTool('evaluate_expression', {
272+
server.registerTool('evaluate_expression', {
262273
description: 'Powerful runtime expression evaluator: Test hypotheses, check computed values, call methods, or inspect object properties in the live debug context. Goes beyond simple variable inspection - evaluate any valid expression in the target language.',
263274
inputSchema: {
264275
expression: z.string().describe('Expression to evaluate in the current programming language context'),
@@ -272,9 +283,9 @@ export class DebugMCPServer {
272283
/**
273284
* Setup MCP resources for documentation
274285
*/
275-
private setupResources() {
286+
private setupResources(server: McpServer) {
276287
// Add MCP resources for debugging documentation
277-
this.mcpServer!.registerResource('Debugging Instructions Guide', 'debugmcp://docs/debug_instructions', {
288+
server.registerResource('Debugging Instructions Guide', 'debugmcp://docs/debug_instructions', {
278289
description: 'Step-by-step instructions for debugging with DebugMCP',
279290
mimeType: 'text/markdown',
280291
}, async (uri: URL) => {
@@ -298,7 +309,7 @@ export class DebugMCPServer {
298309
};
299310

300311
languages.forEach(language => {
301-
this.mcpServer!.registerResource(
312+
server.registerResource(
302313
languageTitles[language],
303314
`debugmcp://docs/troubleshooting/${language}`,
304315
{
@@ -438,20 +449,26 @@ export class DebugMCPServer {
438449
next(error);
439450
});
440451

441-
// Streamable HTTP endpoint — handles MCP protocol messages
452+
// Streamable HTTP endpoint — handles MCP protocol messages.
453+
// A fresh McpServer + transport pair is built per request because
454+
// StreamableHTTPServerTransport in stateless mode (sessionIdGenerator: undefined)
455+
// owns its connection; reusing a single McpServer across requests
456+
// throws "Already connected to a transport" on the second call.
442457
app.post('/mcp', async (req: any, res: any) => {
443458
logger.info('New MCP request received');
444459

460+
const server = this.createMcpServer();
445461
const transport = new StreamableHTTPServerTransport({
446462
sessionIdGenerator: undefined, // Stateless mode - no session management
447463
});
448464
res.on('close', () => {
449465
transport.close();
466+
server.close();
450467
logger.info('MCP transport closed');
451468
});
452469

453470
try {
454-
await this.mcpServer!.connect(transport);
471+
await server.connect(transport);
455472
await transport.handleRequest(req, res, req.body);
456473
} catch (error) {
457474
logger.error('Error while handling MCP request', error);

0 commit comments

Comments
 (0)