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