Skip to main content
Glama

step_over

Execute the current line of code and move to the next line in debugging sessions, allowing you to skip function calls while maintaining program flow control.

Instructions

Step over

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sessionIdYes

Implementation Reference

  • src/server.ts:443-486 (registration)
    Registration of the 'step_over' tool in the MCP server's listTools handler, including schema definition.
    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'] } }, ], }; });
  • Public stepOver method on DebugMcpServer that delegates to SessionManager.stepOver after validation.
    public async stepOver(sessionId: string): Promise<{ success: boolean; state: string; error?: string; data?: unknown; }> { this.validateSession(sessionId); const result = await this.sessionManager.stepOver(sessionId); if (!result.success) { throw new Error(result.error || 'Failed to step over'); } return result;
  • Core tool handler in CallToolRequestSchema switch statement that processes 'step_over' tool calls, invokes server.stepOver, adds source context, and formats response.
    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; }
  • SessionManager's stepOver implementation: validates session state and thread, delegates to _executeStepOperation with DAP 'next' command.
    async stepOver(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 stepOver ${sessionId}] Entered. Current state: ${session.state}, ThreadID: ${threadId}` ); if (!session.proxyManager || !session.proxyManager.isRunning()) { throw new ProxyNotRunningError(sessionId, 'step over'); } if (session.state !== SessionState.PAUSED) { this.logger.warn(`[SM stepOver ${sessionId}] Not paused. State: ${session.state}`); return { success: false, error: 'Not paused', state: session.state }; } if (typeof threadId !== 'number') { this.logger.warn(`[SM stepOver ${sessionId}] No current thread ID.`); return { success: false, error: 'No current thread ID', state: session.state }; } this.logger.info(`[SM stepOver ${sessionId}] Sending DAP 'next' for threadId ${threadId}`); try { return await this._executeStepOperation(session, sessionId, { command: 'next', threadId, logTag: 'stepOver', successMessage: 'Step completed.', }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`[SM stepOver ${sessionId}] Error during step:`, error); this._updateSessionState(session, SessionState.ERROR); return { success: false, error: errorMessage, state: session.state }; } }
  • Executes the actual DAP step command ('next' for step_over), waits for stopped event, retrieves new location from stack trace, handles timeouts and session events.
    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