Skip to main content
Glama

start_debugging

Initiate debugging sessions for Python, JavaScript/Node.js, and Rust programs to set breakpoints, inspect variables, and analyze stack traces using the Debug Adapter Protocol.

Instructions

Start debugging a script

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sessionIdYes
scriptPathYesPath to the script to debug. Use absolute paths or paths relative to your current working directory
argsNo
dapLaunchArgsNo
dryRunSpawnNo
adapterLaunchConfigNoOptional adapter-specific launch configuration overrides

Implementation Reference

  • Primary handler function for the 'start_debugging' tool. Validates the session exists and is active, checks if the script file exists (with container path resolution), logs the file check, and delegates the actual debugging start to the SessionManager.
    public async startDebugging( sessionId: string, scriptPath: string, args?: string[], dapLaunchArgs?: Partial<DebugProtocol.LaunchRequestArguments>, dryRunSpawn?: boolean, adapterLaunchConfig?: Record<string, unknown> ): Promise<{ success: boolean; state: string; error?: string; data?: unknown; errorType?: string; errorCode?: number; }> { this.validateSession(sessionId); // Check script file exists for immediate feedback const fileCheck = await this.fileChecker.checkExists(scriptPath); if (!fileCheck.exists) { throw new McpError(McpErrorCode.InvalidParams, `Script file not found: '${scriptPath}'\nLooked for: '${fileCheck.effectivePath}'${fileCheck.errorMessage ? `\nError: ${fileCheck.errorMessage}` : ''}`); } this.logger.info(`[DebugMcpServer.startDebugging] Script file exists: ${fileCheck.effectivePath} (original: ${scriptPath})`); // Pass the effective path (which has been resolved for container) to session manager const result = await this.sessionManager.startDebugging( sessionId, fileCheck.effectivePath, args, dapLaunchArgs, dryRunSpawn, adapterLaunchConfig ); return result; }
  • JSON schema definition for the 'start_debugging' tool, including parameters like sessionId (required), scriptPath (required), optional args, dapLaunchArgs, dryRunSpawn, and adapterLaunchConfig.
    { name: 'start_debugging', description: 'Start debugging a script', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, scriptPath: { type: 'string', description: scriptPathDescription }, args: { type: 'array', items: { type: 'string' } }, dapLaunchArgs: { type: 'object', properties: { stopOnEntry: { type: 'boolean' }, justMyCode: { type: 'boolean' } }, additionalProperties: true }, dryRunSpawn: { type: 'boolean' }, adapterLaunchConfig: { type: 'object', description: 'Optional adapter-specific launch configuration overrides', additionalProperties: true } }, required: ['sessionId', 'scriptPath'] }
  • src/server.ts:488-921 (registration)
    Registration of the CallTool request handler which dispatches 'start_debugging' calls via switch statement to the startDebugging method, with logging and error handling.
    this.server.setRequestHandler( CallToolRequestSchema, async (request): Promise<ServerResult> => { const toolName = request.params.name; const args = request.params.arguments as ToolArguments; // Log tool call with structured logging this.logger.info('tool:call', { tool: toolName, sessionId: args.sessionId, sessionName: args.sessionId ? this.getSessionName(args.sessionId) : undefined, request: this.sanitizeRequest(args as Record<string, unknown>), timestamp: Date.now() }); try { let result: ServerResult; switch (toolName) { case 'create_debug_session': { // Ensure requested language is among dynamically supported ones const supported = await this.getSupportedLanguagesAsync(); const lang = (args.language || 'python') as DebugLanguage; const requested = lang as unknown as string; const isContainer = process.env.MCP_CONTAINER === 'true'; const allowInContainer = isContainer && requested === 'python'; if (!allowInContainer && !supported.includes(lang)) { throw new UnsupportedLanguageError(lang, supported); } const sessionInfo = await this.createDebugSession({ language: lang, name: args.name, executablePath: args.executablePath }); // Log session creation this.logger.info('session:created', { sessionId: sessionInfo.id, sessionName: sessionInfo.name, language: sessionInfo.language, executablePath: args.executablePath, timestamp: Date.now() }); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, sessionId: sessionInfo.id, message: `Created ${sessionInfo.language} debug session: ${sessionInfo.name}` }) }] }; break; } case 'list_debug_sessions': { result = await this.handleListDebugSessions(); break; } case 'set_breakpoint': { if (!args.sessionId || !args.file || args.line === undefined) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { const breakpoint = await this.setBreakpoint(args.sessionId, args.file, args.line, args.condition); // Log breakpoint event this.logger.info('debug:breakpoint', { event: 'set', sessionId: args.sessionId, sessionName: this.getSessionName(args.sessionId), breakpointId: breakpoint.id, file: breakpoint.file, line: breakpoint.line, verified: breakpoint.verified, timestamp: Date.now() }); // Try to get line context for the breakpoint let context; try { const lineContext = await this.lineReader.getLineContext( breakpoint.file, breakpoint.line, { contextLines: 2 } ); if (lineContext) { context = { lineContent: lineContext.lineContent, surrounding: lineContext.surrounding }; } } catch (contextError) { // Log but don't fail if we can't get context this.logger.debug('Could not get line context for breakpoint', { file: breakpoint.file, line: breakpoint.line, error: contextError }); } result = { content: [{ type: 'text', text: JSON.stringify({ success: true, breakpointId: breakpoint.id, file: breakpoint.file, line: breakpoint.line, verified: breakpoint.verified, message: breakpoint.message || `Breakpoint set at ${breakpoint.file}:${breakpoint.line}`, // Only add warning if there's a message from debugpy (indicating a problem) warning: breakpoint.message || undefined, // Include context if available context: context || undefined }) }] }; const contentEntry = Array.isArray(result.content) ? result.content[0] : undefined; const textContent = contentEntry && typeof (contentEntry as { text?: unknown }).text === 'string' ? (contentEntry as { text: string }).text : undefined; let parsedResponse: Record<string, unknown> | null = null; if (typeof textContent === 'string') { try { parsedResponse = JSON.parse(textContent) as Record<string, unknown>; } catch { parsedResponse = null; } } this.logger.info('tool:set_breakpoint:result', { sessionId: args.sessionId, response: parsedResponse }); } catch (error) { // Handle session state errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || (error.message.includes('not found') && error.message.includes('Session')))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw all other errors (including file validation errors) throw error; } } break; } case 'start_debugging': { if (!args.sessionId || !args.scriptPath) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { if (args.adapterLaunchConfig !== undefined) { const cfg = args.adapterLaunchConfig; if (cfg === null || typeof cfg !== 'object' || Array.isArray(cfg)) { throw new McpError(McpErrorCode.InvalidParams, 'adapterLaunchConfig must be an object when provided'); } } const debugResult = await this.startDebugging( args.sessionId, args.scriptPath, args.args, args.dapLaunchArgs, args.dryRunSpawn, args.adapterLaunchConfig ); const responsePayload: Record<string, unknown> = { success: debugResult.success, state: debugResult.state, message: debugResult.error ? debugResult.error : (debugResult.data as Record<string, unknown>)?.message || `Operation status for ${args.scriptPath}`, }; if (debugResult.data) { responsePayload.data = debugResult.data; } result = { content: [{ type: 'text', text: JSON.stringify(responsePayload) }] }; } catch (error) { // Handle session state errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || (error.message.includes('not found') && error.message.includes('Session')))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message, state: 'stopped' }) }] }; } else { // Re-throw all other errors (including file validation errors) throw error; } } break; } case 'close_debug_session': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } const sessionName = this.getSessionName(args.sessionId); const sessionCreatedAt = Date.now(); // In real implementation, would track creation time const closed = await this.closeDebugSession(args.sessionId); if (closed) { // Log session closure this.logger.info('session:closed', { sessionId: args.sessionId, sessionName: sessionName, duration: Date.now() - sessionCreatedAt, timestamp: Date.now() }); } result = { content: [{ type: 'text', text: JSON.stringify({ success: closed, message: closed ? `Closed debug session: ${args.sessionId}` : `Failed to close debug session: ${args.sessionId}` }) }] }; break; } case 'step_over': case 'step_into': case 'step_out': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } try { let stepResult: { success: boolean; state: string; error?: string; data?: unknown; }; if (toolName === 'step_over') { stepResult = await this.stepOver(args.sessionId); } else if (toolName === 'step_into') { stepResult = await this.stepInto(args.sessionId); } else { stepResult = await this.stepOut(args.sessionId); } // Build response with location and line context if available const stepType = toolName.replace('step_', '').replace('_', ' '); const response: Record<string, unknown> = { success: stepResult.success, message: `Stepped ${stepType}`, state: stepResult.state }; // Extract location from result data const resultData = stepResult.data as { message?: string; location?: { file: string; line: number; column?: number } } | undefined; const location = resultData?.location; if (location) { response.location = location; // Try to get line context try { const lineContext = await this.lineReader.getLineContext( location.file, location.line, { contextLines: 2 } ); if (lineContext) { response.context = { lineContent: lineContext.lineContent, surrounding: lineContext.surrounding }; } } catch (contextError) { // Log but don't fail if we can't get context this.logger.debug('Could not get line context for step result', { file: location.file, line: location.line, error: contextError }); } } result = { content: [{ type: 'text', text: JSON.stringify(response) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof SessionTerminatedError || error instanceof SessionNotFoundError || error instanceof ProxyNotRunningError) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else if (error instanceof Error) { // Handle other expected errors (like "Failed to step over") result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'continue_execution': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } try { const continueResult = await this.continueExecution(args.sessionId); result = { content: [{ type: 'text', text: JSON.stringify({ success: continueResult, message: continueResult ? 'Continued execution' : 'Failed to continue execution' }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof SessionTerminatedError || error instanceof SessionNotFoundError || error instanceof ProxyNotRunningError) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else if (error instanceof Error) { // Handle other expected errors result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'pause_execution': { result = await this.handlePause(args as { sessionId: string }); break; } case 'get_variables': { if (!args.sessionId || args.scope === undefined) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { const variables = await this.getVariables(args.sessionId, args.scope); // Log variable inspection (truncate large values) const truncatedVars = variables.map(v => ({ name: v.name, type: v.type, value: v.value.length > 200 ? v.value.substring(0, 200) + '... (truncated)' : v.value })); this.logger.info('debug:variables', { sessionId: args.sessionId, sessionName: this.getSessionName(args.sessionId), variablesReference: args.scope, variableCount: variables.length, variables: truncatedVars.slice(0, 10), // Log first 10 variables timestamp: Date.now() }); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, variables, count: variables.length, variablesReference: args.scope }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof SessionTerminatedError || error instanceof SessionNotFoundError || error instanceof ProxyNotRunningError) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'get_stack_trace': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } try { // Default to false for cleaner output const includeInternals = args.includeInternals ?? false; const stackFrames = await this.getStackTrace(args.sessionId, includeInternals); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, stackFrames, count: stackFrames.length, includeInternals }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof SessionTerminatedError || error instanceof SessionNotFoundError || error instanceof ProxyNotRunningError) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'get_scopes': { if (!args.sessionId || args.frameId === undefined) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { const scopes = await this.getScopes(args.sessionId, args.frameId); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, scopes }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof SessionTerminatedError || error instanceof SessionNotFoundError || error instanceof ProxyNotRunningError) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'evaluate_expression': { result = await this.handleEvaluateExpression(args as { sessionId: string; expression: string }); break; } case 'get_source_context': { result = await this.handleGetSourceContext(args as { sessionId: string; file: string; line: number; linesContext?: number }); break; } case 'get_local_variables': { result = await this.handleGetLocalVariables(args as { sessionId: string; includeSpecial?: boolean }); break; } case 'list_supported_languages': { result = await this.handleListSupportedLanguages(); break; } default: throw new McpError(McpErrorCode.MethodNotFound, `Unknown tool: ${toolName}`); } // Log successful tool response this.logger.info('tool:response', { tool: toolName, sessionId: args.sessionId, sessionName: args.sessionId ? this.getSessionName(args.sessionId) : undefined, success: true, timestamp: Date.now() }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Log tool error this.logger.error('tool:error', { tool: toolName, sessionId: args.sessionId, sessionName: args.sessionId ? this.getSessionName(args.sessionId) : undefined, error: errorMessage, timestamp: Date.now() }); if (error instanceof McpError) throw error; throw new McpError(McpErrorCode.InternalError, `Failed to execute tool ${toolName}: ${errorMessage}`); } } );
  • src/server.ts:432-486 (registration)
    Registration of ListTools request handler which advertises the 'start_debugging' tool including its schema.
    this.server.setRequestHandler(ListToolsRequestSchema, async () => { this.logger.debug('Handling ListToolsRequest'); // Get supported languages dynamically - deferred until request time const supportedLanguages = await this.getSupportedLanguagesAsync(); // Generate dynamic descriptions for path parameters const fileDescription = this.getPathDescription('source file'); const scriptPathDescription = this.getPathDescription('script'); return { tools: [ { name: 'create_debug_session', description: 'Create a new debugging session', inputSchema: { type: 'object', properties: { language: { type: 'string', enum: supportedLanguages, description: 'Programming language for debugging' }, name: { type: 'string', description: 'Optional session name' }, executablePath: {type: 'string', description: 'Path to language executable (optional, will auto-detect if not provided)'} }, required: ['language'] } }, { name: 'list_supported_languages', description: 'List all supported debugging languages with metadata', inputSchema: { type: 'object', properties: {} } }, { name: 'list_debug_sessions', description: 'List all active debugging sessions', inputSchema: { type: 'object', properties: {} } }, { name: 'set_breakpoint', description: 'Set a breakpoint. Setting breakpoints on non-executable lines (structural, declarative) may lead to unexpected behavior', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, file: { type: 'string', description: fileDescription }, line: { type: 'number', description: 'Line number where to set breakpoint. Executable statements (assignments, function calls, conditionals, returns) work best. Structural lines (function/class definitions), declarative lines (imports), or non-executable lines (comments, blank lines) may cause unexpected stepping behavior' }, condition: { type: 'string' } }, required: ['sessionId', 'file', 'line'] } }, { name: 'start_debugging', description: 'Start debugging a script', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, scriptPath: { type: 'string', description: scriptPathDescription }, args: { type: 'array', items: { type: 'string' } }, dapLaunchArgs: { type: 'object', properties: { stopOnEntry: { type: 'boolean' }, justMyCode: { type: 'boolean' } }, additionalProperties: true }, dryRunSpawn: { type: 'boolean' }, adapterLaunchConfig: { type: 'object', description: 'Optional adapter-specific launch configuration overrides', additionalProperties: true } }, required: ['sessionId', 'scriptPath'] } }, { name: 'close_debug_session', description: 'Close a debugging session', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'step_over', description: 'Step over', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'step_into', description: 'Step into', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'step_out', description: 'Step out', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'continue_execution', description: 'Continue execution', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'pause_execution', description: 'Pause execution (Not Implemented)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'get_variables', description: 'Get variables (scope is variablesReference: number)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, scope: { type: 'number', description: "The variablesReference number from a StackFrame or Variable" } }, required: ['sessionId', 'scope'] } }, { name: 'get_local_variables', description: 'Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, includeSpecial: { type: 'boolean', description: 'Include special/internal variables like this, __proto__, __builtins__, etc. Default: false' } }, required: ['sessionId'] } }, { name: 'get_stack_trace', description: 'Get stack trace', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, includeInternals: { type: 'boolean', description: 'Include internal/framework frames (e.g., Node.js internals). Default: false for cleaner output.' } }, required: ['sessionId'] } }, { name: 'get_scopes', description: 'Get scopes for a stack frame', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, frameId: { type: 'number', description: "The ID of the stack frame from a stackTrace response" } }, required: ['sessionId', 'frameId'] } }, { name: 'evaluate_expression', description: 'Evaluate expression in the current debug context. Expressions can read and modify program state', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, expression: { type: 'string' }, frameId: { type: 'number', description: 'Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically' } }, required: ['sessionId', 'expression'] } }, { name: 'get_source_context', description: 'Get source context around a specific line in a file', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, file: { type: 'string', description: fileDescription }, line: { type: 'number', description: 'Line number to get context for' }, linesContext: { type: 'number', description: 'Number of lines before and after to include (default: 5)' } }, required: ['sessionId', 'file', 'line'] } }, ], }; });
  • src/server.ts:386-389 (registration)
    Invocation of registerTools() in DebugMcpServer constructor, which sets up the MCP tool handlers and schemas for start_debugging.
    this.sessionManager = new SessionManager(sessionManagerConfig, dependencies); this.registerTools(); this.server.onerror = (error) => {

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/debugmcpdev/mcp-debugger'

If you have feedback or need assistance with the MCP directory API, please join our Discord server