Skip to main content
Glama

get_local_variables

Retrieve local variables from the current stack frame during debugging sessions to inspect program state and identify issues.

Instructions

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

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sessionIdYes
includeSpecialNoInclude special/internal variables like this, __proto__, __builtins__, etc. Default: false

Implementation Reference

  • src/server.ts:432-486 (registration)
    Tool registration and schema definition in the ListTools request handler. Includes the inputSchema for get_local_variables.
    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'] } }, ], }; });
  • Dispatch for get_local_variables tool call in the main CallTool request handler switch statement.
    case 'get_local_variables': { result = await this.handleGetLocalVariables(args as { sessionId: string; includeSpecial?: boolean }); break; }
  • Dedicated handler function for get_local_variables tool: validates input, calls session manager method, adds metadata like frame/scope, handles errors, returns formatted MCP ServerResult.
    private async handleGetLocalVariables(args: { sessionId: string; includeSpecial?: boolean }): Promise<ServerResult> { try { // Validate session this.validateSession(args.sessionId); // Get local variables using the new convenience method const result = await this.getLocalVariables( args.sessionId, args.includeSpecial ?? false ); // Log for debugging this.logger.info('tool:get_local_variables', { sessionId: args.sessionId, sessionName: this.getSessionName(args.sessionId), includeSpecial: args.includeSpecial ?? false, variableCount: result.variables.length, frame: result.frame, scopeName: result.scopeName, timestamp: Date.now() }); // Format response const response: Record<string, unknown> = { success: true, variables: result.variables, count: result.variables.length }; // Include frame information if available if (result.frame) { response.frame = result.frame; } // Include scope name if available if (result.scopeName) { response.scopeName = result.scopeName; } // Add helpful messages for edge cases if (result.variables.length === 0) { if (!result.frame) { response.message = 'No stack frames available. The debugger may not be paused.'; } else if (!result.scopeName) { response.message = 'No local scope found in the current frame.'; } else { response.message = `The ${result.scopeName} scope is empty.`; } } return { content: [{ type: 'text', text: JSON.stringify(response) }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Log the error this.logger.error('tool:get_local_variables:error', { sessionId: args.sessionId, error: errorMessage, timestamp: Date.now() }); // 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('not paused'))) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message, message: 'Cannot get local variables. The session must be paused at a breakpoint.' }) }] }; } else if (error instanceof McpError) { throw error; } else { // Wrap unexpected errors throw new McpError(McpErrorCode.InternalError, `Failed to get local variables: ${errorMessage}`); } } }
  • Public wrapper method on DebugMcpServer that validates session and delegates to SessionManager.getLocalVariables
    public async getLocalVariables(sessionId: string, includeSpecial: boolean = false): Promise<{ variables: Variable[]; frame: { name: string; file: string; line: number } | null; scopeName: string | null; }> { this.validateSession(sessionId); return this.sessionManager.getLocalVariables(sessionId, includeSpecial); }
  • Core implementation in SessionManagerData: orchestrates DAP 'stackTrace', 'scopes', 'variables' requests, collects data across stack frames, uses language-specific AdapterPolicy to extract just local variables.
    async getLocalVariables(sessionId: string, includeSpecial: boolean = false): Promise<{ variables: Variable[]; frame: { name: string; file: string; line: number } | null; scopeName: string | null; }> { const session = this._getSessionById(sessionId); this.logger.info(`[SM getLocalVariables ${sessionId}] Entered. includeSpecial: ${includeSpecial}, Current state: ${session.state}`); // Validate session state if (!session.proxyManager || !session.proxyManager.isRunning()) { this.logger.warn(`[SM getLocalVariables ${sessionId}] No active proxy.`); return { variables: [], frame: null, scopeName: null }; } if (session.state !== SessionState.PAUSED) { this.logger.warn(`[SM getLocalVariables ${sessionId}] Session not paused. State: ${session.state}.`); return { variables: [], frame: null, scopeName: null }; } try { // Step 1: Get stack trace const stackFrames = await this.getStackTrace(sessionId); if (!stackFrames || stackFrames.length === 0) { this.logger.warn(`[SM getLocalVariables ${sessionId}] No stack frames available.`); return { variables: [], frame: null, scopeName: null }; } const topFrame = stackFrames[0]; this.logger.info(`[SM getLocalVariables ${sessionId}] Top frame: ${topFrame.name} at ${topFrame.file}:${topFrame.line}`); // Step 2: Collect all scopes for all frames (may need multiple frames for closures) const scopesMap: Record<number, DebugProtocol.Scope[]> = {}; for (const frame of stackFrames) { const scopes = await this.getScopes(sessionId, frame.id); if (scopes && scopes.length > 0) { scopesMap[frame.id] = scopes; } } // Step 3: Collect variables for all scopes const variablesMap: Record<number, Variable[]> = {}; for (const frameId in scopesMap) { const scopes = scopesMap[frameId]; for (const scope of scopes) { if (scope.variablesReference > 0) { const variables = await this.getVariables(sessionId, scope.variablesReference); if (variables && variables.length > 0) { variablesMap[scope.variablesReference] = variables; } } } } // Step 4: Get the appropriate adapter policy const policy = this.selectPolicy(session.language); // Step 5: Extract local variables using the adapter policy let localVars: Variable[] = []; let scopeName: string | null = null; if (policy.extractLocalVariables) { localVars = policy.extractLocalVariables(stackFrames, scopesMap, variablesMap, includeSpecial); // Get the scope name for reporting if (policy.getLocalScopeName) { const scopeNames = policy.getLocalScopeName(); scopeName = Array.isArray(scopeNames) ? scopeNames[0] : scopeNames; } } else { // Fallback: use first non-global scope from top frame const topFrameScopes = scopesMap[topFrame.id] || []; const localScope = topFrameScopes.find(s => !s.name.toLowerCase().includes('global')); if (localScope) { localVars = variablesMap[localScope.variablesReference] || []; scopeName = localScope.name; } } this.logger.info(`[SM getLocalVariables ${sessionId}] Found ${localVars.length} local variables.`); return { variables: localVars, frame: { name: topFrame.name, file: topFrame.file, line: topFrame.line }, scopeName }; } catch (error) { this.logger.error(`[SM getLocalVariables ${sessionId}] Error getting local variables:`, error); return { variables: [], frame: null, scopeName: null }; } }

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