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
| Name | Required | Description | Default |
|---|---|---|---|
| sessionId | Yes |
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'] } }, ], }; });
- src/server.ts:475-475 (schema)Input schema for step_out tool: requires sessionId{ name: 'step_out', description: 'Step out', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } },
- src/server.ts:341-348 (handler)Server wrapper method for step_out tool dispatching to SessionManagerpublic 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; }
- src/server.ts:692-762 (handler)MCP CallToolRequest handler dispatch for step_out (and siblings), validates, calls stepOut, formats response with location/contextcase '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 _executeStepOperationasync 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/errorsprivate _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 }); }); }); }