Skip to main content
Glama

step_out

Execute the current function to completion and return to its caller during debugging sessions, enabling efficient navigation through program execution flow.

Instructions

Step out

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sessionIdYes

Implementation Reference

  • src/server.ts:473-486 (registration)
    MCP tool registration for step_over, step_into, step_out including input schema requiring 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'] } }, ], }; });
  • Input schema for step_out tool: requires sessionId
    { name: 'step_out', description: 'Step out', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } },
  • Server wrapper method for step_out tool dispatching to SessionManager
    public async stepOut(sessionId: string): Promise<{ success: boolean; state: string; error?: string; data?: unknown; }> { this.validateSession(sessionId); const result = await this.sessionManager.stepOut(sessionId); if (!result.success) { throw new Error(result.error || 'Failed to step out'); } return result; }
  • MCP CallToolRequest handler dispatch for step_out (and siblings), validates, calls stepOut, formats response with location/context
    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; } }
  • Core handler implementation for step_out MCP tool: validates paused state and current thread, dispatches DAP stepOut request via shared _executeStepOperation
    async stepOut(sessionId: string): Promise<DebugResult> { const session = this._getSessionById(sessionId); // Check if session is terminated if (session.sessionLifecycle === SessionLifecycleState.TERMINATED) { throw new SessionTerminatedError(sessionId); } const threadId = session.proxyManager?.getCurrentThreadId(); this.logger.info( `[SM stepOut ${sessionId}] Entered. Current state: ${session.state}, ThreadID: ${threadId}` ); if (!session.proxyManager || !session.proxyManager.isRunning()) { throw new ProxyNotRunningError(sessionId, 'step out'); } if (session.state !== SessionState.PAUSED) { this.logger.warn(`[SM stepOut ${sessionId}] Not paused. State: ${session.state}`); return { success: false, error: 'Not paused', state: session.state }; } if (typeof threadId !== 'number') { this.logger.warn(`[SM stepOut ${sessionId}] No current thread ID.`); return { success: false, error: 'No current thread ID', state: session.state }; } this.logger.info(`[SM stepOut ${sessionId}] Sending DAP 'stepOut' for threadId ${threadId}`); try { return await this._executeStepOperation(session, sessionId, { command: 'stepOut', threadId, logTag: 'stepOut', successMessage: 'Step out completed.', }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`[SM stepOut ${sessionId}] Error during step:`, error); this._updateSessionState(session, SessionState.ERROR); return { success: false, error: errorMessage, state: session.state }; } }
  • Shared helper for executing step operations (stepOver/Into/Out): sends DAP request via proxyManager.sendDapRequest(command, {threadId}), waits for 'stopped'/'terminated'/'exited' events, captures new location from stackTrace, handles timeouts/errors
    private _executeStepOperation( session: ManagedSession, sessionId: string, options: { command: 'next' | 'stepIn' | 'stepOut'; threadId: number; logTag: string; successMessage: string; terminatedMessage?: string; exitedMessage?: string; } ): Promise<DebugResult> { const proxyManager = session.proxyManager; if (!proxyManager) { return Promise.resolve({ success: false, error: 'Proxy manager unavailable', state: session.state, }); } const terminatedMessage = options.terminatedMessage ?? 'Step completed as session terminated.'; const exitedMessage = options.exitedMessage ?? 'Step completed as session exited.'; return new Promise((resolve) => { let settled = false; const cleanup = () => { proxyManager.off('stopped', onStopped); proxyManager.off('terminated', onTerminated); proxyManager.off('exited', onExited); proxyManager.off('exit', onExit); clearTimeout(timeout); }; const settle = (result: DebugResult) => { if (settled) { return; } settled = true; cleanup(); resolve(result); }; const success = (message: string, location?: { file: string; line: number; column?: number }) => { this.logger.info(`[SM ${options.logTag} ${sessionId}] ${message} Current state: ${session.state}`); const data: { message: string; location?: { file: string; line: number; column?: number } } = { message }; if (location) { data.location = location; } settle({ success: true, state: session.state, data, }); }; const onStopped = async () => { // Try to get current location from stack trace let location: { file: string; line: number; column?: number } | undefined; try { // Wait a brief moment for state to settle after stopped event await new Promise(resolve => setTimeout(resolve, 10)); const stackFrames = await this.getStackTrace(sessionId); if (stackFrames && stackFrames.length > 0) { const topFrame = stackFrames[0]; location = { file: topFrame.file, line: topFrame.line, column: topFrame.column }; this.logger.debug(`[SM ${options.logTag} ${sessionId}] Captured location: ${location.file}:${location.line}`); } } catch (error) { // Log but don't fail the step operation if we can't get location this.logger.debug(`[SM ${options.logTag} ${sessionId}] Could not capture location:`, error); } success(options.successMessage, location); }; const onTerminated = () => success(terminatedMessage); const onExited = () => success(exitedMessage); const onExit = () => success(exitedMessage); const timeout = setTimeout(() => { this.logger.warn( `[SM ${options.logTag} ${sessionId}] Timeout waiting for stopped or termination event` ); settle({ success: false, error: ErrorMessages.stepTimeout(5), state: session.state, }); }, 5000); proxyManager.on('stopped', onStopped); proxyManager.on('terminated', onTerminated); proxyManager.on('exited', onExited); proxyManager.on('exit', onExit); this._updateSessionState(session, SessionState.RUNNING); proxyManager .sendDapRequest(options.command, { threadId: options.threadId }) .catch((error: unknown) => { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error( `[SM ${options.logTag} ${sessionId}] Error during step request:`, error ); this._updateSessionState(session, SessionState.ERROR); settle({ success: false, error: errorMessage, state: session.state }); }); }); }

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