Skip to main content
Glama

evaluate_expression

Evaluate expressions to read or modify program state during debugging sessions, supporting Python, JavaScript, and Rust code inspection.

Instructions

Evaluate expression in the current debug context. Expressions can read and modify program state

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sessionIdYes
expressionYes
frameIdNoOptional 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

Implementation Reference

  • Core handler that executes the evaluation by sending a DAP 'evaluate' request to the debug proxy and processes the response.
    async evaluateExpression( sessionId: string, expression: string, frameId?: number, context: 'watch' | 'repl' | 'hover' | 'clipboard' | 'variables' = 'variables' ): Promise<EvaluateResult> { const session = this._getSessionById(sessionId); this.logger.info( `[SM evaluateExpression ${sessionId}] Entered. Expression: "${this.truncateForLog( expression, 100 )}", frameId: ${frameId}, context: ${context}, state: ${session.state}` ); // Basic sanity checks if (!expression || expression.trim().length === 0) { this.logger.warn(`[SM evaluateExpression ${sessionId}] Empty expression provided`); return { success: false, error: 'Expression cannot be empty' }; } // Validate session state if (!session.proxyManager || !session.proxyManager.isRunning()) { this.logger.warn(`[SM evaluateExpression ${sessionId}] No active proxy or proxy not running`); return { success: false, error: 'No active debug session' }; } if (session.state !== SessionState.PAUSED) { this.logger.warn( `[SM evaluateExpression ${sessionId}] Cannot evaluate: session not paused. State: ${session.state}` ); return { success: false, error: 'Cannot evaluate: debugger not paused. Ensure the debugger is stopped at a breakpoint.', }; } // Handle frameId - get current frame from stack trace if not provided if (frameId === undefined) { try { const threadId = session.proxyManager.getCurrentThreadId(); if (typeof threadId !== 'number') { this.logger.warn( `[SM evaluateExpression ${sessionId}] No current thread ID to get stack trace` ); return { success: false, error: 'Unable to find thread for evaluation. Ensure the debugger is paused at a breakpoint.', }; } this.logger.info( `[SM evaluateExpression ${sessionId}] No frameId provided, getting current frame from stack trace` ); const stackResponse = await session.proxyManager.sendDapRequest<DebugProtocol.StackTraceResponse>( 'stackTrace', { threadId, startFrame: 0, levels: 1, // We only need the first frame } ); if (stackResponse?.body?.stackFrames && stackResponse.body.stackFrames.length > 0) { frameId = stackResponse.body.stackFrames[0].id; this.logger.info( `[SM evaluateExpression ${sessionId}] Using current frame ID: ${frameId} from stack trace` ); } else { this.logger.warn(`[SM evaluateExpression ${sessionId}] No stack frames available`); return { success: false, error: 'No active stack frame. Ensure the debugger is paused at a breakpoint.', }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error( `[SM evaluateExpression ${sessionId}] Error getting stack trace for default frame:`, error ); return { success: false, error: `Unable to determine current frame: ${errorMessage}` }; } } try { // Send DAP evaluate request this.logger.info( `[SM evaluateExpression ${sessionId}] Sending DAP 'evaluate' request. Expression: "${this.truncateForLog( expression, 100 )}", frameId: ${frameId}, context: ${context}` ); const response = await session.proxyManager.sendDapRequest<DebugProtocol.EvaluateResponse>('evaluate', { expression, frameId, context, }); // Log raw response in debug mode this.logger.debug(`[SM evaluateExpression ${sessionId}] DAP evaluate raw response:`, response); // Process response if (response && response.body) { const body = response.body; // Note: debugpy automatically truncates collections at 300 items for performance const result: EvaluateResult = { success: true, result: body.result || '', // Default to empty string if no result type: body.type, // Optional, can be undefined variablesReference: body.variablesReference || 0, // Default to 0 (no children) namedVariables: body.namedVariables, indexedVariables: body.indexedVariables, presentationHint: body.presentationHint, }; // Log the evaluation result with structured logging this.logger.info('debug:evaluate', { event: 'expression', sessionId, sessionName: session.name, expression: this.truncateForLog(expression, 100), frameId, context, result: this.truncateForLog(result.result || '', 1000), type: result.type, variablesReference: result.variablesReference, namedVariables: result.namedVariables, indexedVariables: result.indexedVariables, timestamp: Date.now(), }); this.logger.info( `[SM evaluateExpression ${sessionId}] Evaluation successful. Result: "${this.truncateForLog( result.result || '', 200 )}", Type: ${result.type}, VarRef: ${result.variablesReference}` ); return result; } else { this.logger.warn(`[SM evaluateExpression ${sessionId}] No body in evaluate response`); return { success: false, error: 'No response body from debug adapter' }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Log the error this.logger.error('debug:evaluate', { event: 'error', sessionId, sessionName: session.name, expression: this.truncateForLog(expression, 100), frameId, context, error: errorMessage, timestamp: Date.now(), }); this.logger.error(`[SM evaluateExpression ${sessionId}] Error evaluating expression:`, error); // Determine error type for better user feedback let userError = errorMessage; if (errorMessage.includes('SyntaxError')) { userError = `Syntax error in expression: ${errorMessage}`; } else if (errorMessage.includes('NameError')) { userError = `Name not found: ${errorMessage}`; } else if (errorMessage.includes('TypeError')) { userError = `Type error: ${errorMessage}`; } else if (errorMessage.includes('frame')) { userError = `Invalid frame context: ${errorMessage}`; } return { success: false, error: userError }; }
  • src/server.ts:875-878 (registration)
    MCP tool registration in CallToolRequestSchema handler: routes 'evaluate_expression' tool calls to handleEvaluateExpression.
    case 'evaluate_expression': { result = await this.handleEvaluateExpression(args as { sessionId: string; expression: string }); break; }
  • Input schema definition for 'evaluate_expression' tool returned by ListToolsRequest.
    { 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'] } },
  • Tool wrapper that validates input, calls SessionManager.evaluateExpression, and formats MCP response.
    private async handleEvaluateExpression(args: { sessionId: string, expression: string, frameId?: number }): Promise<ServerResult> { try { // Validate session this.validateSession(args.sessionId); // Check expression length (sanity check) if (args.expression.length > 10240) { throw new McpError(McpErrorCode.InvalidParams, 'Expression too long (max 10KB)'); } // Call SessionManager's evaluateExpression method (uses 'watch' context by default for variable access) const result = await this.sessionManager.evaluateExpression( args.sessionId, args.expression, args.frameId // Let SessionManager use its default context ('watch') for proper variable access ); // Log for audit trail this.logger.info('tool:evaluate_expression', { sessionId: args.sessionId, sessionName: this.getSessionName(args.sessionId), expression: args.expression.substring(0, 100), // Truncate for logging success: result.success, hasResult: !!result.result, timestamp: Date.now() }); // Return formatted response return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Log the error this.logger.error('tool:evaluate_expression:error', { sessionId: args.sessionId, expression: args.expression.substring(0, 100), 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 }) }] }; } else if (error instanceof McpError) { throw error; } else { // Wrap unexpected errors throw new McpError(McpErrorCode.InternalError, `Failed to evaluate expression: ${errorMessage}`); } }
  • Type definition for the output of evaluateExpression (result structure).
    export interface EvaluateResult { success: boolean; result?: string; type?: string; variablesReference?: number; namedVariables?: number; indexedVariables?: number; presentationHint?: DebugProtocol.VariablePresentationHint; error?: string; }

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