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;
    	}
    }

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