@@ -81,7 +81,6 @@ export function isLoopbackOrigin(originHeader: string | undefined): boolean {
8181}
8282
8383export 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