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