Skip to main content
Glama
workbackai

MCP NodeJS Debugger

by workbackai

evaluate

Evaluate JavaScript expressions in the current debugging context to inspect variables and test code behavior during NodeJS debugging sessions.

Instructions

Evaluates a JavaScript expression in the current context

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
expressionYesJavaScript expression to evaluate

Implementation Reference

  • Registers the 'evaluate' MCP tool with name, description, schema, and handler function
    server.tool(
      "evaluate",
      "Evaluates a JavaScript expression in the current context",
      {
        expression: z.string().describe("JavaScript expression to evaluate")
      },
      async ({ expression }) => {
        try {
          // Ensure debugger is enabled
          if (!inspector.debuggerEnabled) {
            await inspector.enableDebugger();
          }
          
          // Capture the current console output length to know where to start capturing new output
          const consoleStartIndex = inspector.consoleOutput.length;
          
          // Wrap the expression in a try-catch to better handle errors
          const wrappedExpression = `
            try {
              ${expression}
            } catch (e) {
              e;  // Return the error
            }
          `;
          
          let result;
          
          if (inspector.paused && inspector.currentCallFrames.length > 0) {
            // When paused at a breakpoint, evaluate in the context of the call frame
            const frame = inspector.currentCallFrames[0];
            result = await inspector.evaluateOnCallFrame(frame.callFrameId, wrappedExpression);
          } else {
            // Otherwise, evaluate in the global context
            result = await inspector.send('Runtime.evaluate', {
              expression: wrappedExpression,
              contextId: 1,
              objectGroup: 'console',
              includeCommandLineAPI: true,
              silent: false,
              returnByValue: true,
              generatePreview: true,
              awaitPromise: true  // This will wait for promises to resolve
            });
          }
          
          // Give some time for console logs to be processed
          await new Promise(resolve => setTimeout(resolve, 200));
          
          // Get any console output that was generated during execution
          const consoleOutputs = inspector.consoleOutput.slice(consoleStartIndex);
          const consoleText = consoleOutputs.map(output => 
            `[${output.type}] ${output.message}`
          ).join('\n');
          
          let valueRepresentation;
          
          if (result.result) {
            if (result.result.type === 'object') {
              if (result.result.value) {
                // If we have a value, use it
                valueRepresentation = JSON.stringify(result.result.value, null, 2);
              } else if (result.result.objectId) {
                // If we have an objectId but no value, the object was too complex to serialize directly
                // Get more details about the object
                try {
                  const objectProps = await inspector.getProperties(result.result.objectId);
                  const formattedObject = {};
                  
                  for (const prop of objectProps.result) {
                    if (prop.value) {
                      if (prop.value.type === 'object' && prop.value.subtype !== 'null') {
                        // For nested objects, try to get their details too
                        if (prop.value.objectId) {
                          try {
                            const nestedProps = await inspector.getProperties(prop.value.objectId);
                            const nestedObj = {};
                            for (const nestedProp of nestedProps.result) {
                              if (nestedProp.value) {
                                if (nestedProp.value.value !== undefined) {
                                  nestedObj[nestedProp.name] = nestedProp.value.value;
                                } else {
                                  nestedObj[nestedProp.name] = nestedProp.value.description || 
                                    `[${nestedProp.value.subtype || nestedProp.value.type}]`;
                                }
                              }
                            }
                            formattedObject[prop.name] = nestedObj;
                          } catch (nestedErr) {
                            formattedObject[prop.name] = prop.value.description || 
                              `[${prop.value.subtype || prop.value.type}]`;
                          }
                        } else {
                          formattedObject[prop.name] = prop.value.description || 
                            `[${prop.value.subtype || prop.value.type}]`;
                        }
                      } else if (prop.value.type === 'function') {
                        formattedObject[prop.name] = '[function]';
                      } else if (prop.value.value !== undefined) {
                        formattedObject[prop.name] = prop.value.value;
                      } else {
                        formattedObject[prop.name] = `[${prop.value.type}]`;
                      }
                    }
                  }
                  
                  valueRepresentation = JSON.stringify(formattedObject, null, 2);
                } catch (propErr) {
                  // If we can't get properties, at least show the object description
                  valueRepresentation = result.result.description || `[${result.result.subtype || result.result.type}]`;
                }
              } else {
                // Fallback for objects without value or objectId
                valueRepresentation = result.result.description || `[${result.result.subtype || result.result.type}]`;
              }
            } else if (result.result.type === 'undefined') {
              valueRepresentation = 'undefined';
            } else if (result.result.value !== undefined) {
              valueRepresentation = result.result.value.toString();
            } else {
              valueRepresentation = `[${result.result.type}]`;
            }
          } else {
            valueRepresentation = 'No result';
          }
          
          // Prepare the response content
          let responseContent = [];
          
          // Add console output if there was any
          if (consoleText.length > 0) {
            responseContent.push({
              type: "text", 
              text: `Console output:\n${consoleText}`
            });
          }
          
          // Add the evaluation result
          responseContent.push({
            type: "text",
            text: `Evaluation result: ${valueRepresentation}`
          });
          
          return { content: responseContent };
        } catch (err) {
          return {
            content: [{
              type: "text",
              text: `Error evaluating expression: ${err.message}`
            }]
          };
        }
      }
    );
  • Input schema defining the 'expression' parameter using Zod validation
    {
      expression: z.string().describe("JavaScript expression to evaluate")
    },
  • Executes the tool: wraps expression, evaluates using CDP Runtime.evaluate or Debugger.evaluateOnCallFrame depending on pause state, captures console output, formats complex results, returns markdown content
    async ({ expression }) => {
      try {
        // Ensure debugger is enabled
        if (!inspector.debuggerEnabled) {
          await inspector.enableDebugger();
        }
        
        // Capture the current console output length to know where to start capturing new output
        const consoleStartIndex = inspector.consoleOutput.length;
        
        // Wrap the expression in a try-catch to better handle errors
        const wrappedExpression = `
          try {
            ${expression}
          } catch (e) {
            e;  // Return the error
          }
        `;
        
        let result;
        
        if (inspector.paused && inspector.currentCallFrames.length > 0) {
          // When paused at a breakpoint, evaluate in the context of the call frame
          const frame = inspector.currentCallFrames[0];
          result = await inspector.evaluateOnCallFrame(frame.callFrameId, wrappedExpression);
        } else {
          // Otherwise, evaluate in the global context
          result = await inspector.send('Runtime.evaluate', {
            expression: wrappedExpression,
            contextId: 1,
            objectGroup: 'console',
            includeCommandLineAPI: true,
            silent: false,
            returnByValue: true,
            generatePreview: true,
            awaitPromise: true  // This will wait for promises to resolve
          });
        }
        
        // Give some time for console logs to be processed
        await new Promise(resolve => setTimeout(resolve, 200));
        
        // Get any console output that was generated during execution
        const consoleOutputs = inspector.consoleOutput.slice(consoleStartIndex);
        const consoleText = consoleOutputs.map(output => 
          `[${output.type}] ${output.message}`
        ).join('\n');
        
        let valueRepresentation;
        
        if (result.result) {
          if (result.result.type === 'object') {
            if (result.result.value) {
              // If we have a value, use it
              valueRepresentation = JSON.stringify(result.result.value, null, 2);
            } else if (result.result.objectId) {
              // If we have an objectId but no value, the object was too complex to serialize directly
              // Get more details about the object
              try {
                const objectProps = await inspector.getProperties(result.result.objectId);
                const formattedObject = {};
                
                for (const prop of objectProps.result) {
                  if (prop.value) {
                    if (prop.value.type === 'object' && prop.value.subtype !== 'null') {
                      // For nested objects, try to get their details too
                      if (prop.value.objectId) {
                        try {
                          const nestedProps = await inspector.getProperties(prop.value.objectId);
                          const nestedObj = {};
                          for (const nestedProp of nestedProps.result) {
                            if (nestedProp.value) {
                              if (nestedProp.value.value !== undefined) {
                                nestedObj[nestedProp.name] = nestedProp.value.value;
                              } else {
                                nestedObj[nestedProp.name] = nestedProp.value.description || 
                                  `[${nestedProp.value.subtype || nestedProp.value.type}]`;
                              }
                            }
                          }
                          formattedObject[prop.name] = nestedObj;
                        } catch (nestedErr) {
                          formattedObject[prop.name] = prop.value.description || 
                            `[${prop.value.subtype || prop.value.type}]`;
                        }
                      } else {
                        formattedObject[prop.name] = prop.value.description || 
                          `[${prop.value.subtype || prop.value.type}]`;
                      }
                    } else if (prop.value.type === 'function') {
                      formattedObject[prop.name] = '[function]';
                    } else if (prop.value.value !== undefined) {
                      formattedObject[prop.name] = prop.value.value;
                    } else {
                      formattedObject[prop.name] = `[${prop.value.type}]`;
                    }
                  }
                }
                
                valueRepresentation = JSON.stringify(formattedObject, null, 2);
              } catch (propErr) {
                // If we can't get properties, at least show the object description
                valueRepresentation = result.result.description || `[${result.result.subtype || result.result.type}]`;
              }
            } else {
              // Fallback for objects without value or objectId
              valueRepresentation = result.result.description || `[${result.result.subtype || result.result.type}]`;
            }
          } else if (result.result.type === 'undefined') {
            valueRepresentation = 'undefined';
          } else if (result.result.value !== undefined) {
            valueRepresentation = result.result.value.toString();
          } else {
            valueRepresentation = `[${result.result.type}]`;
          }
        } else {
          valueRepresentation = 'No result';
        }
        
        // Prepare the response content
        let responseContent = [];
        
        // Add console output if there was any
        if (consoleText.length > 0) {
          responseContent.push({
            type: "text", 
            text: `Console output:\n${consoleText}`
          });
        }
        
        // Add the evaluation result
        responseContent.push({
          type: "text",
          text: `Evaluation result: ${valueRepresentation}`
        });
        
        return { content: responseContent };
      } catch (err) {
        return {
          content: [{
            type: "text",
            text: `Error evaluating expression: ${err.message}`
          }]
        };
      }
    }
  • Helper method on Inspector class to evaluate expression in specific call frame context, used when debugger is paused
    async evaluateOnCallFrame(callFrameId, expression) {
    	if (!this.paused) {
    		throw new Error('Debugger is not paused');
    	}
    	
    	try {
    		return await this.send('Debugger.evaluateOnCallFrame', {
    			callFrameId,
    			expression,
    			objectGroup: 'console',
    			includeCommandLineAPI: true,
    			silent: false,
    			returnByValue: true,
    			generatePreview: true
    		});
    	} catch (err) {
    		throw err;
    	}
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but only states the basic action without disclosing critical behaviors: it doesn't mention security implications, error handling, side effects, or what 'current context' entails (e.g., scope, permissions). This is inadequate for a tool that executes code, risking misuse.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, clear sentence with zero wasted words, front-loading the key action and resource. It's appropriately sized for a simple tool, making it easy to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of code evaluation and the lack of annotations and output schema, the description is insufficient. It doesn't cover return values, error cases, or behavioral nuances, leaving significant gaps for safe and effective use in a debugging context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, with the single parameter 'expression' fully documented in the schema. The description adds no additional meaning beyond the schema, such as examples or constraints on the expression, so it meets the baseline of 3 without compensating for gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('evaluates') and resource ('JavaScript expression') with the context ('in the current context'), making the purpose unambiguous. It doesn't explicitly differentiate from siblings like 'get_console_output' or 'inspect_variables', but the specificity of evaluating code distinguishes it well enough for a 4.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'get_console_output' for output or 'inspect_variables' for variable inspection. It lacks any mention of prerequisites, exclusions, or typical scenarios, leaving usage unclear in the context of debugging tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/workbackai/mcp-nodejs-debugger'

If you have feedback or need assistance with the MCP directory API, please join our Discord server