browser-devtools-mcp 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{platformInfo}from"../core-UYJ4QEJ2.js";import{AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,AWS_PROFILE,AWS_REGION,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,OTEL_ENABLE,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_TYPE,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION}from"../core-22BVGANX.js";import{Command,Option}from"commander";import{ZodFirstPartyTypeKind}from"zod";function _unwrapZodType(zodType){let current=zodType,isOptional=!1,defaultValue;for(;;){let typeName=current._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodOptional)isOptional=!0,current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodDefault)isOptional=!0,defaultValue=current._def.defaultValue(),current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodNullable)isOptional=!0,current=current._def.innerType;else break}return{innerType:current,isOptional,defaultValue}}function _getDescription(zodType){return zodType._def.description}function _toCamelCase(str){return str.replace(/[-_]([a-z])/g,(_,letter)=>letter.toUpperCase())}function _toKebabCase(str){return str.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function _createOption(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName,option;switch(typeName){case ZodFirstPartyTypeKind.ZodString:option=new Option(`--${flagName} <string>`,description);break;case ZodFirstPartyTypeKind.ZodNumber:option=new Option(`--${flagName} <number>`,description),option.argParser(value=>{let n=Number(value);if(!Number.isFinite(n))throw new Error(`Invalid number: ${value}`);return n});break;case ZodFirstPartyTypeKind.ZodBoolean:option=new Option(`--${flagName}`,description);break;case ZodFirstPartyTypeKind.ZodEnum:let enumValues=innerType._def.values;option=new Option(`--${flagName} <choice>`,description).choices(enumValues);break;case ZodFirstPartyTypeKind.ZodArray:option=new Option(`--${flagName} <value...>`,description);break;case ZodFirstPartyTypeKind.ZodObject:case ZodFirstPartyTypeKind.ZodRecord:option=new Option(`--${flagName} <json>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{throw new Error(`Invalid JSON: ${value}`)}});break;case ZodFirstPartyTypeKind.ZodAny:case ZodFirstPartyTypeKind.ZodUnknown:option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;case ZodFirstPartyTypeKind.ZodLiteral:let literalValue=innerType._def.value;typeof literalValue=="boolean"?option=new Option(`--${flagName}`,description):(option=new Option(`--${flagName} <value>`,description),option.default(literalValue));break;case ZodFirstPartyTypeKind.ZodUnion:let unionOptions=innerType._def.options;if(unionOptions.every(opt=>opt._def.typeName===ZodFirstPartyTypeKind.ZodLiteral)){let choices=unionOptions.map(opt=>String(opt._def.value));option=new Option(`--${flagName} <choice>`,description).choices(choices)}else option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;default:option=new Option(`--${flagName} <value>`,description);break}return defaultValue!==void 0&&option.default(defaultValue),!isOptional&&defaultValue===void 0&&option.makeOptionMandatory(!0),option}function _generateOptionsFromSchema(schema){let options=[];for(let[name,zodType]of Object.entries(schema)){let option=_createOption(name,zodType);option&&options.push(option)}return options}function _parseOptionsToToolInput(options){let result={};for(let[key,value]of Object.entries(options)){let camelKey=_toCamelCase(key);value!==void 0&&(result[camelKey]=value)}return result}function _parseToolName(toolName){let underscoreIndex=toolName.indexOf("_");return underscoreIndex===-1?{domain:"default",commandName:toolName}:{domain:toolName.substring(0,underscoreIndex),commandName:toolName.substring(underscoreIndex+1)}}function registerToolCommands(program,tools2,handler){let domainCommands=new Map;for(let tool of tools2){let{domain,commandName}=_parseToolName(tool.name()),domainCommand=domainCommands.get(domain);domainCommand||(domainCommand=new Command(domain).description(`${domain.charAt(0).toUpperCase()+domain.slice(1)} commands`),domainCommands.set(domain,domainCommand),program.addCommand(domainCommand));let toolCommand=new Command(commandName).description(tool.description().trim()),options=_generateOptionsFromSchema(tool.inputSchema());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{createRequire}from"node:module";import*as path from"node:path";import*as readline from"node:readline";import{fileURLToPath}from"node:url";import{Command as Command2,Option as Option2}from"commander";var require2=createRequire(import.meta.url),__filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename),cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools,DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=path.join(__dirname,"..","daemon-server.js"),env=_buildDaemonEnv(opts);_debug(`Starting daemon server from: ${daemonServerPath}`),_debug(`Daemon port: ${opts.port}`);let child=spawn(process.execPath,[daemonServerPath,"--port",String(opts.port)],{detached:!0,stdio:"ignore",env});child.unref(),_debug(`Daemon process spawned with PID: ${child.pid}`),_output(`Started daemon server as detached process (PID: ${child.pid})`)}async function _ensureDaemonRunning(opts){if(await _isDaemonRunning(opts.port))_debug("Daemon is already running");else{_output(`Daemon server is not running on port ${opts.port}, starting...`),_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;_debug(`Waiting for daemon to be ready (max ${maxRetries} retries, ${retryDelay}ms delay)`);for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),_debug(`Retry ${i+1}/${maxRetries}: checking daemon status...`),await _isDaemonRunning(opts.port)){_debug("Daemon is now ready"),_output("Daemon server is ready");return}throw new Error(`Daemon server failed to start within ${maxRetries*retryDelay/1e3} seconds`)}}async function _stopDaemon(port,timeout){try{return(await fetch(`http://localhost:${port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _callTool(port,toolName,toolInput,sessionId,timeout){let headers={"Content-Type":"application/json"};sessionId&&(headers["session-id"]=sessionId);let request={toolName,toolInput};_debug(`Calling tool: ${toolName}`),_debug("Tool input:",toolInput),_debug(`Session ID: ${sessionId||"(default)"}`),_debug(`Timeout: ${timeout||"none"}`);let startTime=Date.now(),response=await fetch(`http://localhost:${port}/call`,{method:"POST",headers,body:JSON.stringify(request),signal:timeout?AbortSignal.timeout(timeout):void 0}),duration=Date.now()-startTime;if(_debug(`Tool call completed in ${duration}ms, status: ${response.status}`),!response.ok){let errorBody=await response.json().catch(()=>({}));throw _debug("Tool call error:",errorBody),new Error(errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
|
|
2
|
+
import{platformInfo}from"../core-74IS3ZCO.js";import{AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,AWS_PROFILE,AWS_REGION,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,OTEL_ENABLE,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_TYPE,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION}from"../core-22BVGANX.js";import{Command,Option}from"commander";import{ZodFirstPartyTypeKind}from"zod";function _unwrapZodType(zodType){let current=zodType,isOptional=!1,defaultValue;for(;;){let typeName=current._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodOptional)isOptional=!0,current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodDefault)isOptional=!0,defaultValue=current._def.defaultValue(),current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodNullable)isOptional=!0,current=current._def.innerType;else break}return{innerType:current,isOptional,defaultValue}}function _getDescription(zodType){return zodType._def.description}function _toCamelCase(str){return str.replace(/[-_]([a-z])/g,(_,letter)=>letter.toUpperCase())}function _toKebabCase(str){return str.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function _createOption(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName,option;switch(typeName){case ZodFirstPartyTypeKind.ZodString:option=new Option(`--${flagName} <string>`,description);break;case ZodFirstPartyTypeKind.ZodNumber:option=new Option(`--${flagName} <number>`,description),option.argParser(value=>{let n=Number(value);if(!Number.isFinite(n))throw new Error(`Invalid number: ${value}`);return n});break;case ZodFirstPartyTypeKind.ZodBoolean:option=new Option(`--${flagName}`,description);break;case ZodFirstPartyTypeKind.ZodEnum:let enumValues=innerType._def.values;option=new Option(`--${flagName} <choice>`,description).choices(enumValues);break;case ZodFirstPartyTypeKind.ZodArray:option=new Option(`--${flagName} <value...>`,description);break;case ZodFirstPartyTypeKind.ZodObject:case ZodFirstPartyTypeKind.ZodRecord:option=new Option(`--${flagName} <json>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{throw new Error(`Invalid JSON: ${value}`)}});break;case ZodFirstPartyTypeKind.ZodAny:case ZodFirstPartyTypeKind.ZodUnknown:option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;case ZodFirstPartyTypeKind.ZodLiteral:let literalValue=innerType._def.value;typeof literalValue=="boolean"?option=new Option(`--${flagName}`,description):(option=new Option(`--${flagName} <value>`,description),option.default(literalValue));break;case ZodFirstPartyTypeKind.ZodUnion:let unionOptions=innerType._def.options;if(unionOptions.every(opt=>opt._def.typeName===ZodFirstPartyTypeKind.ZodLiteral)){let choices=unionOptions.map(opt=>String(opt._def.value));option=new Option(`--${flagName} <choice>`,description).choices(choices)}else option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;default:option=new Option(`--${flagName} <value>`,description);break}return defaultValue!==void 0&&option.default(defaultValue),!isOptional&&defaultValue===void 0&&option.makeOptionMandatory(!0),option}function _generateOptionsFromSchema(schema){let options=[];for(let[name,zodType]of Object.entries(schema)){let option=_createOption(name,zodType);option&&options.push(option)}return options}function _parseOptionsToToolInput(options){let result={};for(let[key,value]of Object.entries(options)){let camelKey=_toCamelCase(key);value!==void 0&&(result[camelKey]=value)}return result}function _parseToolName(toolName){let underscoreIndex=toolName.indexOf("_");return underscoreIndex===-1?{domain:"default",commandName:toolName}:{domain:toolName.substring(0,underscoreIndex),commandName:toolName.substring(underscoreIndex+1)}}function registerToolCommands(program,tools2,handler){let domainCommands=new Map;for(let tool of tools2){let{domain,commandName}=_parseToolName(tool.name()),domainCommand=domainCommands.get(domain);domainCommand||(domainCommand=new Command(domain).description(`${domain.charAt(0).toUpperCase()+domain.slice(1)} commands`),domainCommands.set(domain,domainCommand),program.addCommand(domainCommand));let toolCommand=new Command(commandName).description(tool.description().trim()),options=_generateOptionsFromSchema(tool.inputSchema());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{createRequire}from"node:module";import*as path from"node:path";import*as readline from"node:readline";import{fileURLToPath}from"node:url";import{Command as Command2,Option as Option2}from"commander";var require2=createRequire(import.meta.url),__filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename),cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools,DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=path.join(__dirname,"..","daemon-server.js"),env=_buildDaemonEnv(opts);_debug(`Starting daemon server from: ${daemonServerPath}`),_debug(`Daemon port: ${opts.port}`);let child=spawn(process.execPath,[daemonServerPath,"--port",String(opts.port)],{detached:!0,stdio:"ignore",env});child.unref(),_debug(`Daemon process spawned with PID: ${child.pid}`),_output(`Started daemon server as detached process (PID: ${child.pid})`)}async function _ensureDaemonRunning(opts){if(await _isDaemonRunning(opts.port))_debug("Daemon is already running");else{_output(`Daemon server is not running on port ${opts.port}, starting...`),_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;_debug(`Waiting for daemon to be ready (max ${maxRetries} retries, ${retryDelay}ms delay)`);for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),_debug(`Retry ${i+1}/${maxRetries}: checking daemon status...`),await _isDaemonRunning(opts.port)){_debug("Daemon is now ready"),_output("Daemon server is ready");return}throw new Error(`Daemon server failed to start within ${maxRetries*retryDelay/1e3} seconds`)}}async function _stopDaemon(port,timeout){try{return(await fetch(`http://localhost:${port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _callTool(port,toolName,toolInput,sessionId,timeout){let headers={"Content-Type":"application/json"};sessionId&&(headers["session-id"]=sessionId);let request={toolName,toolInput};_debug(`Calling tool: ${toolName}`),_debug("Tool input:",toolInput),_debug(`Session ID: ${sessionId||"(default)"}`),_debug(`Timeout: ${timeout||"none"}`);let startTime=Date.now(),response=await fetch(`http://localhost:${port}/call`,{method:"POST",headers,body:JSON.stringify(request),signal:timeout?AbortSignal.timeout(timeout):void 0}),duration=Date.now()-startTime;if(_debug(`Tool call completed in ${duration}ms, status: ${response.status}`),!response.ok){let errorBody=await response.json().catch(()=>({}));throw _debug("Tool call error:",errorBody),new Error(errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
|
|
3
3
|
`).map(line=>`${prefix}${line}`).join(`
|
|
4
4
|
`);if(typeof output=="number"||typeof output=="boolean")return`${prefix}${output}`;if(Array.isArray(output))return output.length===0?`${prefix}[]`:output.map(item=>_formatOutput(item,indent)).join(`
|
|
5
5
|
`);if(typeof output=="object"){let lines=[];for(let[key,value]of Object.entries(output))value!==void 0&&(typeof value=="object"&&value!==null&&!Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):lines.push(`${prefix}${key}: ${value}`));return lines.join(`
|
|
@@ -1117,13 +1117,13 @@ Notes:
|
|
|
1117
1117
|
- Execution blocks the Node event loop until the script completes
|
|
1118
1118
|
- Long-running scripts will block the process; use short expressions
|
|
1119
1119
|
- Return value must be serializable (JSON-compatible)
|
|
1120
|
-
`}inputSchema(){return{script:z81.string().describe("JavaScript code to execute in the Node process."),timeoutMs:z81.number().int().min(100).max(3e4).describe("Max evaluation time in milliseconds (default: 5000, max: 30000).").optional()}}outputSchema(){return{result:z81.any().describe("The result of the evaluation. Can be primitives, arrays, or objects. Must be serializable (JSON-compatible).")}}async handle(context,args){return{result:await evaluateInNode(context.storeKey,args.script,args.timeoutMs??5e3)}}};var tools14=[new RunJsInNode];import{EventEmitter}from"node:events";import{WebSocket}from"ws";var NodeCDPSession=class extends EventEmitter{ws=null;reqId=0;pending=new Map;connected=!1;wsUrl;constructor(wsUrl){super(),this.wsUrl=wsUrl}async connect(){if(
|
|
1120
|
+
`}inputSchema(){return{script:z81.string().describe("JavaScript code to execute in the Node process."),timeoutMs:z81.number().int().min(100).max(3e4).describe("Max evaluation time in milliseconds (default: 5000, max: 30000).").optional()}}outputSchema(){return{result:z81.any().describe("The result of the evaluation. Can be primitives, arrays, or objects. Must be serializable (JSON-compatible).")}}async handle(context,args){return{result:await evaluateInNode(context.storeKey,args.script,args.timeoutMs??5e3)}}};var tools14=[new RunJsInNode];import{EventEmitter}from"node:events";import{WebSocket}from"ws";var NodeCDPSession=class extends EventEmitter{ws=null;reqId=0;pending=new Map;connected=!1;wsUrl;connectOptions;constructor(wsUrl,connectOptions){super(),this.wsUrl=wsUrl,this.connectOptions=connectOptions}async connect(){if(this.connected)return;let wsOptions=this.connectOptions?.headers?{headers:this.connectOptions.headers}:void 0;return new Promise((resolve,reject)=>{this.ws=new WebSocket(this.wsUrl,wsOptions);let timeout=setTimeout(()=>{reject(new Error(`Connection timeout to ${this.wsUrl}`)),this.ws?.close()},1e4);this.ws.on("open",()=>{clearTimeout(timeout),this.connected=!0,resolve()}),this.ws.on("error",err=>{clearTimeout(timeout),this.connected||reject(err)}),this.ws.on("close",()=>{this.connected=!1;for(let[id,handler]of this.pending)handler.reject(new Error("CDP session closed"));this.pending.clear()}),this.ws.on("message",data=>{try{let msg=JSON.parse(data.toString());this._handleMessage(msg)}catch{}})})}async send(method,params){if(!this.ws||!this.connected)throw new Error("CDP session is not connected");let id=++this.reqId;return new Promise((resolve,reject)=>{this.pending.set(id,{resolve,reject});let message=JSON.stringify({id,method,params:params||{}});this.ws.send(message,err=>{err&&(this.pending.delete(id),reject(err))}),setTimeout(()=>{this.pending.has(id)&&(this.pending.delete(id),reject(new Error(`CDP command timeout: ${method}`)))},3e4)})}async detach(){if(this.ws){this.connected=!1,this.ws.close(),this.ws=null;for(let[,handler]of this.pending)handler.reject(new Error("CDP session detached"));this.pending.clear()}}isConnected(){return this.connected}_handleMessage(msg){if(msg.id!==void 0){let handler=this.pending.get(msg.id);handler&&(this.pending.delete(msg.id),msg.error?handler.reject(new Error(`CDP error: ${msg.error.message||JSON.stringify(msg.error)}`)):handler.resolve(msg.result||{}))}else msg.method&&this.emit(msg.method,msg.params)}};async function createNodeCDPSession(wsUrl,connectOptions){let session=new NodeCDPSession(wsUrl,connectOptions);return await session.connect(),session}import{execSync}from"node:child_process";import*as http from"node:http";var DEFAULT_HOST="127.0.0.1",DEFAULT_PORT=9229,DEFAULT_TIMEOUT_MS7=1e4,ACTIVATION_POLL_INTERVAL_MS=200,MAX_INSPECTOR_SCAN_PORTS=20;function buildInspectorWebSocketUrl(host,port,targetWebSocketDebuggerUrl){let path4=new URL(targetWebSocketDebuggerUrl).pathname;return`ws://${host}:${port}${path4}`}var DISCOVERY_HOST_HEADER="127.0.0.1:9229";function inspectorWsConnectOptions(wsUrl){try{let hostname=new URL(wsUrl).hostname.toLowerCase();return hostname==="127.0.0.1"||hostname==="localhost"?void 0:{headers:{Host:DISCOVERY_HOST_HEADER}}}catch{return}}async function discoverInspectorTargets(host=DEFAULT_HOST,port=DEFAULT_PORT,timeoutMs=5e3){return new Promise((resolve,reject)=>{let timeout=setTimeout(()=>{reject(new Error(`Inspector discovery timeout on ${host}:${port}`))},timeoutMs),req=http.get({host,port,path:"/json/list",...host!==DEFAULT_HOST&&host!=="localhost"&&{headers:{Host:DISCOVERY_HOST_HEADER}}},res=>{let data="";res.on("data",chunk=>{data+=chunk}),res.on("end",()=>{clearTimeout(timeout);try{let targets=JSON.parse(data);resolve(targets)}catch{reject(new Error(`Invalid response from inspector at ${host}:${port}`))}})});req.on("error",err=>{clearTimeout(timeout),reject(new Error(`Cannot reach inspector at ${host}:${port}: ${err.message}`))}),req.end()})}async function scanForInspector(host=DEFAULT_HOST,startPort=DEFAULT_PORT,maxPorts=MAX_INSPECTOR_SCAN_PORTS){for(let port=startPort;port<startPort+maxPorts;port++)try{let targets=await discoverInspectorTargets(host,port,1e3);if(targets.length>0)return{port,targets}}catch{}return null}function findNodeProcesses(namePattern){try{let platform=process.platform,output;platform==="win32"?output=execSync(`wmic process where "name like '%node%'" get ProcessId,CommandLine /format:csv`,{encoding:"utf-8",timeout:5e3}):output=execSync('ps aux | grep -E "[n]ode|[t]s-node|[n]px"',{encoding:"utf-8",timeout:5e3});let processes=[];for(let line of output.split(`
|
|
1121
1121
|
`)){let trimmed=line.trim();if(trimmed)if(platform==="win32"){let parts=trimmed.split(",");if(parts.length>=3){let pid=parseInt(parts[parts.length-1],10),command=parts.slice(1,-1).join(",");!isNaN(pid)&&pid>0&&processes.push({pid,command})}}else{let parts=trimmed.split(/\s+/);if(parts.length>=11){let pid=parseInt(parts[1],10),command=parts.slice(10).join(" ");!isNaN(pid)&&pid>0&&processes.push({pid,command})}}}if(namePattern){let regex=new RegExp(namePattern,"i");return processes.filter(p=>regex.test(p.command))}return processes}catch{return[]}}async function activateInspector(pid,host=DEFAULT_HOST,timeoutMs=DEFAULT_TIMEOUT_MS7){if(process.platform==="win32")throw new Error("SIGUSR1 activation is not supported on Windows. Start the Node.js process with --inspect flag or NODE_OPTIONS=--inspect instead.");try{process.kill(pid,0)}catch{throw new Error(`Process ${pid} does not exist or is not accessible`)}debug(`Sending SIGUSR1 to process ${pid} to activate V8 Inspector...`),process.kill(pid,"SIGUSR1");let startTime=Date.now();for(;Date.now()-startTime<timeoutMs;){let result=await scanForInspector(host,DEFAULT_PORT,10);if(result)return debug(`Inspector activated on port ${result.port} for process ${pid}`),result;await new Promise(resolve=>setTimeout(resolve,ACTIVATION_POLL_INTERVAL_MS))}throw new Error(`Timeout waiting for inspector to activate on process ${pid}`)}function resolveContainerId(containerName){try{let ids=execSync(`docker ps -q -f name=${containerName}`,{encoding:"utf-8",timeout:5e3}).trim().split(`
|
|
1122
1122
|
`).filter(Boolean);return ids.length===0?null:(ids.length>1&&debug(`Multiple containers match "${containerName}", using first: ${ids[0]}`),ids[0])}catch(err){if(/Cannot connect to the Docker daemon|docker: command not found/i.test(err.message??""))throw new Error(`${err.message} When MCP runs in a container, mount /var/run/docker.sock and ensure the docker CLI is available.`);return null}}async function activateInspectorInContainer(containerId,host=DEFAULT_HOST,port=DEFAULT_PORT,timeoutMs=DEFAULT_TIMEOUT_MS7){if(process.platform==="win32")throw new Error("Docker container activation may have limitations on Windows. Ensure the container has port 9229 exposed and Node is started with --inspect.");let hostPid="";try{let lines=execSync(`docker top ${containerId}`,{encoding:"utf-8",timeout:5e3}).trim().split(`
|
|
1123
|
-
`);if(lines.length<2)throw new Error(`No Node.js process found inside container ${containerId}`);for(let i=1;i<lines.length;i++){let line=lines[i];if(!/node/i.test(line))continue;let parts=line.trim().split(/\s+/);if(parts.length>=2&&/^\d+$/.test(parts[1])){hostPid=parts[1];break}}if(!hostPid)throw new Error(`Could not parse Node PID from container ${containerId}`)}catch(err){if(err.message?.includes("No Node.js process"))throw err;let dockerHint=/Cannot connect to the Docker daemon|docker: command not found/i.test(err.message??"")?" When MCP runs in a container, mount /var/run/docker.sock and ensure the docker CLI is available.":"";throw new Error(`Cannot find Node process in container ${containerId}: ${err.message}. Ensure Docker is running and the container has a Node.js process.${dockerHint}`)}debug(`Sending SIGUSR1 to Node process (host PID ${hostPid}) for container ${containerId}...`);try{execSync(`docker run --rm --pid=host alpine kill -USR1 ${hostPid}`,{encoding:"utf-8",timeout:1e4})}catch(err){let dockerHint=/Cannot connect to the Docker daemon|docker: command not found/i.test(err.message??"")?" When MCP runs in a container, mount /var/run/docker.sock and ensure the docker CLI is available.":"";throw new Error(`Failed to send SIGUSR1 to process in container ${containerId}: ${err.message}. (Uses docker run --rm --pid=host alpine kill -USR1 <hostPid>; ensure alpine image is available.)${dockerHint}`)}let startTime=Date.now();for(;Date.now()-startTime<timeoutMs;){try{let targets=await discoverInspectorTargets(host,port,2e3);if(targets.length>0)return debug(`Inspector active at ${host}:${port} for container ${containerId}`),{port,targets}}catch(err){debug(`Inspector not yet ready at ${host}:${port}: ${err instanceof Error?err.message:String(err)}`)}await new Promise(resolve=>setTimeout(resolve,ACTIVATION_POLL_INTERVAL_MS))}throw new Error(`Timeout waiting for inspector from container ${containerId} at ${host}:${port}. Ensure: (1) the container has NODE_OPTIONS=--inspect-port=0.0.0.0:9229 so inspector accepts external connections when activated via SIGUSR1; (2) the container exposes port 9229 to the host (e.g. -p 30019:9229); (3) when MCP runs in a container, host is reachable (e.g. NODE_INSPECTOR_HOST=host.docker.internal and on Linux add extra_hosts: host.docker.internal:host-gateway).`)}async function connectToNodeProcess(options){let host=options.host??NODE_INSPECTOR_HOST??DEFAULT_HOST,timeoutMs=options.timeoutMs||DEFAULT_TIMEOUT_MS7,activateIfNeeded=options.activateIfNeeded!==!1;if(options.wsUrl)return debug(`Connecting to Node.js inspector via WebSocket: ${options.wsUrl}`),{session:await createNodeCDPSession(options.wsUrl),processInfo:{pid:0,inspectorActive:!0,webSocketUrl:options.wsUrl},target:{id:"direct",type:"node",title:"Direct WebSocket connection",url:"",webSocketDebuggerUrl:options.wsUrl}};let containerId=options.containerId??null;if(!containerId&&options.containerName&&(containerId=resolveContainerId(options.containerName),!containerId))throw new Error(`No running container found matching name: ${options.containerName}`);if(containerId){let port2=options.port||DEFAULT_PORT,targets2=[];try{targets2=await discoverInspectorTargets(host,port2,3e3)}catch{}if(targets2.length===0&&activateIfNeeded&&(debug(`Inspector not active. Activating via SIGUSR1 in container ${containerId}...`),targets2=(await activateInspectorInContainer(containerId,host,port2,timeoutMs)).targets),targets2.length===0)throw new Error(`Cannot connect to Node.js in container ${containerId}. Inspector not active. Ensure port 9229 is exposed (e.g. -p 9229:9229) and use activateIfNeeded or start Node with --inspect.`);let target=targets2[0],wsUrl=buildInspectorWebSocketUrl(host,port2,target.webSocketDebuggerUrl);return{session:await createNodeCDPSession(wsUrl),processInfo:{pid:0,inspectorActive:!0,inspectorHost:host,inspectorPort:port2,webSocketUrl:wsUrl,containerId},target}}let pid=options.pid,processCommand;if(!pid&&options.processName){let processes=findNodeProcesses(options.processName);if(processes.length===0)throw new Error(`No Node.js process found matching: ${options.processName}`);if(processes.length>1){let procList=processes.map(p=>` PID ${p.pid}: ${p.command}`).join(`
|
|
1123
|
+
`);if(lines.length<2)throw new Error(`No Node.js process found inside container ${containerId}`);for(let i=1;i<lines.length;i++){let line=lines[i];if(!/node/i.test(line))continue;let parts=line.trim().split(/\s+/);if(parts.length>=2&&/^\d+$/.test(parts[1])){hostPid=parts[1];break}}if(!hostPid)throw new Error(`Could not parse Node PID from container ${containerId}`)}catch(err){if(err.message?.includes("No Node.js process"))throw err;let dockerHint=/Cannot connect to the Docker daemon|docker: command not found/i.test(err.message??"")?" When MCP runs in a container, mount /var/run/docker.sock and ensure the docker CLI is available.":"";throw new Error(`Cannot find Node process in container ${containerId}: ${err.message}. Ensure Docker is running and the container has a Node.js process.${dockerHint}`)}debug(`Sending SIGUSR1 to Node process (host PID ${hostPid}) for container ${containerId}...`);try{execSync(`docker run --rm --pid=host alpine kill -USR1 ${hostPid}`,{encoding:"utf-8",timeout:1e4})}catch(err){let dockerHint=/Cannot connect to the Docker daemon|docker: command not found/i.test(err.message??"")?" When MCP runs in a container, mount /var/run/docker.sock and ensure the docker CLI is available.":"";throw new Error(`Failed to send SIGUSR1 to process in container ${containerId}: ${err.message}. (Uses docker run --rm --pid=host alpine kill -USR1 <hostPid>; ensure alpine image is available.)${dockerHint}`)}let startTime=Date.now();for(;Date.now()-startTime<timeoutMs;){try{let targets=await discoverInspectorTargets(host,port,2e3);if(targets.length>0)return debug(`Inspector active at ${host}:${port} for container ${containerId}`),{port,targets}}catch(err){debug(`Inspector not yet ready at ${host}:${port}: ${err instanceof Error?err.message:String(err)}`)}await new Promise(resolve=>setTimeout(resolve,ACTIVATION_POLL_INTERVAL_MS))}throw new Error(`Timeout waiting for inspector from container ${containerId} at ${host}:${port}. Ensure: (1) the container has NODE_OPTIONS=--inspect-port=0.0.0.0:9229 so inspector accepts external connections when activated via SIGUSR1; (2) the container exposes port 9229 to the host (e.g. -p 30019:9229); (3) when MCP runs in a container, host is reachable (e.g. NODE_INSPECTOR_HOST=host.docker.internal and on Linux add extra_hosts: host.docker.internal:host-gateway).`)}async function connectToNodeProcess(options){let host=options.host??NODE_INSPECTOR_HOST??DEFAULT_HOST,timeoutMs=options.timeoutMs||DEFAULT_TIMEOUT_MS7,activateIfNeeded=options.activateIfNeeded!==!1;if(options.wsUrl)return debug(`Connecting to Node.js inspector via WebSocket: ${options.wsUrl}`),{session:await createNodeCDPSession(options.wsUrl,inspectorWsConnectOptions(options.wsUrl)),processInfo:{pid:0,inspectorActive:!0,webSocketUrl:options.wsUrl},target:{id:"direct",type:"node",title:"Direct WebSocket connection",url:"",webSocketDebuggerUrl:options.wsUrl}};let containerId=options.containerId??null;if(!containerId&&options.containerName&&(containerId=resolveContainerId(options.containerName),!containerId))throw new Error(`No running container found matching name: ${options.containerName}`);if(containerId){let port2=options.port||DEFAULT_PORT,targets2=[];try{targets2=await discoverInspectorTargets(host,port2,3e3)}catch{}if(targets2.length===0&&activateIfNeeded&&(debug(`Inspector not active. Activating via SIGUSR1 in container ${containerId}...`),targets2=(await activateInspectorInContainer(containerId,host,port2,timeoutMs)).targets),targets2.length===0)throw new Error(`Cannot connect to Node.js in container ${containerId}. Inspector not active. Ensure port 9229 is exposed (e.g. -p 9229:9229) and use activateIfNeeded or start Node with --inspect.`);let target=targets2[0],wsUrl=buildInspectorWebSocketUrl(host,port2,target.webSocketDebuggerUrl);return{session:await createNodeCDPSession(wsUrl,inspectorWsConnectOptions(wsUrl)),processInfo:{pid:0,inspectorActive:!0,inspectorHost:host,inspectorPort:port2,webSocketUrl:wsUrl,containerId},target}}let pid=options.pid,processCommand;if(!pid&&options.processName){let processes=findNodeProcesses(options.processName);if(processes.length===0)throw new Error(`No Node.js process found matching: ${options.processName}`);if(processes.length>1){let procList=processes.map(p=>` PID ${p.pid}: ${p.command}`).join(`
|
|
1124
1124
|
`);throw new Error(`Multiple Node.js processes match "${options.processName}":
|
|
1125
1125
|
${procList}
|
|
1126
|
-
Please specify a PID directly.`)}pid=processes[0].pid,processCommand=processes[0].command,debug(`Found Node.js process: PID ${pid} - ${processCommand}`)}let port=options.port||DEFAULT_PORT,targets=[];try{targets=await discoverInspectorTargets(host,port,3e3)}catch{}if(targets.length>0){let target=targets[0];debug(`Inspector already active at ${host}:${port}, connecting...`);let wsUrl=buildInspectorWebSocketUrl(host,port,target.webSocketDebuggerUrl);return{session:await createNodeCDPSession(wsUrl),processInfo:{pid:pid||0,command:processCommand,inspectorActive:!0,inspectorHost:host,inspectorPort:port,webSocketUrl:wsUrl},target}}if(pid&&activateIfNeeded){debug(`Inspector not active on ${host}:${port}. Activating via SIGUSR1 on PID ${pid}...`);let{port:activePort,targets:activeTargets}=await activateInspector(pid,host,timeoutMs);if(activeTargets.length===0)throw new Error(`Inspector activated on port ${activePort} but no targets found`);let target=activeTargets[0],wsUrl=buildInspectorWebSocketUrl(host,activePort,target.webSocketDebuggerUrl);return{session:await createNodeCDPSession(wsUrl),processInfo:{pid,command:processCommand,inspectorActive:!0,inspectorHost:host,inspectorPort:activePort,webSocketUrl:wsUrl},target}}throw pid?new Error(`Cannot connect to process ${pid}. Inspector is not active and activation failed. Try starting the process with NODE_OPTIONS=--inspect or --inspect flag.`):new Error("No target specified. Provide a PID, process name, port, or WebSocket URL. If the process is not started with --inspect, provide a PID to activate via SIGUSR1.")}var NodeToolSessionContext=class{_sessionId;_connection=null;_storeKey=null;closed=!1;constructor(sessionId){this._sessionId=sessionId}sessionId(){return this._sessionId}async connect(options){if(this._connection)throw new Error("Already connected to a Node.js process. Disconnect first.");debug(`Connecting to Node.js process with options: ${JSON.stringify(options)}`);let result=await connectToNodeProcess(options);return this._connection=result,this._storeKey=getStoreKey(result.processInfo.pid,result.processInfo.webSocketUrl),await enableDebugging(this._storeKey,result.session,{pid:result.processInfo.pid,command:result.processInfo.command}),debug(`Connected to Node.js process: PID=${result.processInfo.pid}, store=${this._storeKey}`),{processInfo:result.processInfo,target:result.target}}async disconnect(){!this._connection||!this._storeKey||(debug(`Disconnecting from Node.js process: ${this._storeKey}`),await detachDebugging(this._storeKey),await this._connection.session.detach(),this._connection=null,this._storeKey=null)}get storeKey(){if(!this._storeKey)throw new Error("Not connected to any Node.js process. Call connect first.");return this._storeKey}get isConnected(){return this._connection!==null&&this._connection.session.isConnected()}get processInfo(){return this._connection?.processInfo??null}async close(){return this.closed?!1:(await this.disconnect(),this.closed=!0,!0)}};var NodeToolExecutor=class{sessionIdProvider;sessionContext;constructor(sessionIdProvider){this.sessionIdProvider=sessionIdProvider}_sessionContext(){if(!this.sessionContext){let sessionId=this.sessionIdProvider();this.sessionContext=new NodeToolSessionContext(sessionId),debug(`Created Node.js session context for session ${sessionId}`)}return this.sessionContext}async executeTool(tool,args){debug(`Executing Node.js tool ${tool.name()} with input: ${toJson(args)}`);try{let context=this._sessionContext(),result=await tool.handle(context,args);return debug(`Executed Node.js tool ${tool.name()} successfully`),result}catch(err){throw debug(`Error executing Node.js tool ${tool.name()}: ${err}`),err}}};var tools15=[...tools13,...tools14];function createToolExecutor2(sessionIdProvider){return new NodeToolExecutor(sessionIdProvider)}var NodeCliProvider=class{platform="node";cliName="node-devtools-cli";tools=tools15;sessionDescription="Manage Node.js debugging sessions";cliExamples=["node-devtools-cli interactive","node-devtools-cli connect --pid 12345"];bashCompletionOptions="";bashCompletionCommands="daemon session tools config completion interactive connect disconnect status tracepoint logpoint exceptionpoint watch";zshCompletionOptions="";zshCompletionCommands=[{name:"daemon",description:"Manage the daemon server"},{name:"session",description:"Manage Node.js debug sessions"},{name:"tools",description:"List and inspect available tools"},{name:"config",description:"Show current configuration"},{name:"completion",description:"Generate shell completion scripts"},{name:"interactive",description:"Start interactive REPL mode"},{name:"connect",description:"Connect to a Node.js process"},{name:"disconnect",description:"Disconnect from current process"},{name:"status",description:"Show connection status"},{name:"tracepoint",description:"Tracepoint commands"},{name:"logpoint",description:"Logpoint commands"},{name:"exceptionpoint",description:"Exceptionpoint commands"},{name:"watch",description:"Watch expression commands"}];replPrompt="node> ";cliDescription="Node.js DevTools MCP CLI";packageName="browser-devtools-mcp";buildEnv(_opts){return{...process.env}}addOptions(cmd){return cmd}getConfig(){return{consoleMessagesBufferSize:NODE_CONSOLE_MESSAGES_BUFFER_SIZE,nodeInspectorHost:NODE_INSPECTOR_HOST}}formatConfig(configValues){return[" Node.js:",` Console Messages Buffer: ${configValues.consoleMessagesBufferSize??NODE_CONSOLE_MESSAGES_BUFFER_SIZE}`,` Inspector Host (NODE_INSPECTOR_HOST): ${configValues.nodeInspectorHost??"(default 127.0.0.1)"}`].join(`
|
|
1126
|
+
Please specify a PID directly.`)}pid=processes[0].pid,processCommand=processes[0].command,debug(`Found Node.js process: PID ${pid} - ${processCommand}`)}let port=options.port||DEFAULT_PORT,targets=[];try{targets=await discoverInspectorTargets(host,port,3e3)}catch{}if(targets.length>0){let target=targets[0];debug(`Inspector already active at ${host}:${port}, connecting...`);let wsUrl=buildInspectorWebSocketUrl(host,port,target.webSocketDebuggerUrl);return{session:await createNodeCDPSession(wsUrl,inspectorWsConnectOptions(wsUrl)),processInfo:{pid:pid||0,command:processCommand,inspectorActive:!0,inspectorHost:host,inspectorPort:port,webSocketUrl:wsUrl},target}}if(pid&&activateIfNeeded){debug(`Inspector not active on ${host}:${port}. Activating via SIGUSR1 on PID ${pid}...`);let{port:activePort,targets:activeTargets}=await activateInspector(pid,host,timeoutMs);if(activeTargets.length===0)throw new Error(`Inspector activated on port ${activePort} but no targets found`);let target=activeTargets[0],wsUrl=buildInspectorWebSocketUrl(host,activePort,target.webSocketDebuggerUrl);return{session:await createNodeCDPSession(wsUrl,inspectorWsConnectOptions(wsUrl)),processInfo:{pid,command:processCommand,inspectorActive:!0,inspectorHost:host,inspectorPort:activePort,webSocketUrl:wsUrl},target}}throw pid?new Error(`Cannot connect to process ${pid}. Inspector is not active and activation failed. Try starting the process with NODE_OPTIONS=--inspect or --inspect flag.`):new Error("No target specified. Provide a PID, process name, port, or WebSocket URL. If the process is not started with --inspect, provide a PID to activate via SIGUSR1.")}var NodeToolSessionContext=class{_sessionId;_connection=null;_storeKey=null;closed=!1;constructor(sessionId){this._sessionId=sessionId}sessionId(){return this._sessionId}async connect(options){if(this._connection)throw new Error("Already connected to a Node.js process. Disconnect first.");debug(`Connecting to Node.js process with options: ${JSON.stringify(options)}`);let result=await connectToNodeProcess(options);return this._connection=result,this._storeKey=getStoreKey(result.processInfo.pid,result.processInfo.webSocketUrl),await enableDebugging(this._storeKey,result.session,{pid:result.processInfo.pid,command:result.processInfo.command}),debug(`Connected to Node.js process: PID=${result.processInfo.pid}, store=${this._storeKey}`),{processInfo:result.processInfo,target:result.target}}async disconnect(){!this._connection||!this._storeKey||(debug(`Disconnecting from Node.js process: ${this._storeKey}`),await detachDebugging(this._storeKey),await this._connection.session.detach(),this._connection=null,this._storeKey=null)}get storeKey(){if(!this._storeKey)throw new Error("Not connected to any Node.js process. Call connect first.");return this._storeKey}get isConnected(){return this._connection!==null&&this._connection.session.isConnected()}get processInfo(){return this._connection?.processInfo??null}async close(){return this.closed?!1:(await this.disconnect(),this.closed=!0,!0)}};var NodeToolExecutor=class{sessionIdProvider;sessionContext;constructor(sessionIdProvider){this.sessionIdProvider=sessionIdProvider}_sessionContext(){if(!this.sessionContext){let sessionId=this.sessionIdProvider();this.sessionContext=new NodeToolSessionContext(sessionId),debug(`Created Node.js session context for session ${sessionId}`)}return this.sessionContext}async executeTool(tool,args){debug(`Executing Node.js tool ${tool.name()} with input: ${toJson(args)}`);try{let context=this._sessionContext(),result=await tool.handle(context,args);return debug(`Executed Node.js tool ${tool.name()} successfully`),result}catch(err){throw debug(`Error executing Node.js tool ${tool.name()}: ${err}`),err}}};var tools15=[...tools13,...tools14];function createToolExecutor2(sessionIdProvider){return new NodeToolExecutor(sessionIdProvider)}var NodeCliProvider=class{platform="node";cliName="node-devtools-cli";tools=tools15;sessionDescription="Manage Node.js debugging sessions";cliExamples=["node-devtools-cli interactive","node-devtools-cli connect --pid 12345"];bashCompletionOptions="";bashCompletionCommands="daemon session tools config completion interactive connect disconnect status tracepoint logpoint exceptionpoint watch";zshCompletionOptions="";zshCompletionCommands=[{name:"daemon",description:"Manage the daemon server"},{name:"session",description:"Manage Node.js debug sessions"},{name:"tools",description:"List and inspect available tools"},{name:"config",description:"Show current configuration"},{name:"completion",description:"Generate shell completion scripts"},{name:"interactive",description:"Start interactive REPL mode"},{name:"connect",description:"Connect to a Node.js process"},{name:"disconnect",description:"Disconnect from current process"},{name:"status",description:"Show connection status"},{name:"tracepoint",description:"Tracepoint commands"},{name:"logpoint",description:"Logpoint commands"},{name:"exceptionpoint",description:"Exceptionpoint commands"},{name:"watch",description:"Watch expression commands"}];replPrompt="node> ";cliDescription="Node.js DevTools MCP CLI";packageName="browser-devtools-mcp";buildEnv(_opts){return{...process.env}}addOptions(cmd){return cmd}getConfig(){return{consoleMessagesBufferSize:NODE_CONSOLE_MESSAGES_BUFFER_SIZE,nodeInspectorHost:NODE_INSPECTOR_HOST}}formatConfig(configValues){return[" Node.js:",` Console Messages Buffer: ${configValues.consoleMessagesBufferSize??NODE_CONSOLE_MESSAGES_BUFFER_SIZE}`,` Inspector Host (NODE_INSPECTOR_HOST): ${configValues.nodeInspectorHost??"(default 127.0.0.1)"}`].join(`
|
|
1127
1127
|
`)}formatConfigForRepl(_opts){return[` console-messages-buffer = ${NODE_CONSOLE_MESSAGES_BUFFER_SIZE}`,` node-inspector-host = ${NODE_INSPECTOR_HOST??"127.0.0.1"}`].join(`
|
|
1128
1128
|
`)}},cliProvider2=new NodeCliProvider;var SERVER_INSTRUCTIONS2=`
|
|
1129
1129
|
Node.js Backend Debugging Platform
|
package/dist/daemon-server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{platformInfo}from"./core-
|
|
1
|
+
import{platformInfo}from"./core-74IS3ZCO.js";import{DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,debug,enable,error,info,isDebugEnabled}from"./core-22BVGANX.js";import{createRequire}from"node:module";import{Command,Option,InvalidOptionArgumentError}from"commander";import{serve}from"@hono/node-server";import{Hono}from"hono";import{cors}from"hono/cors";import{z}from"zod";var require2=createRequire(import.meta.url),daemonStartTime=0,daemonPort=0,app=new Hono,sessions=new Map,DEFAULT_SESSION_ID="#default",ERRORS={get sessionNotFound(){return _buildErrorResponse(404,"Session Not Found")},get toolNotFound(){return _buildErrorResponse(404,"Tool Not Found")},get internalServerError(){return _buildErrorResponse(500,"Internal Server Error")}};function _buildErrorResponse(code,message){return{error:{code,message}}}async function _closeSession(session){if(session.closed=!0,session.context)try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(session.id)}function _createSession(ctx,sessionId){let now=Date.now(),session={id:sessionId,toolExecutor:platformInfo.toolsInfo.createToolExecutor(()=>sessionId),closed:!1,createdAt:now,lastActiveAt:now};return debug(`Created session with id ${sessionId}`),session}function _getSessionInfo(session){let now=Date.now();return{id:session.id,createdAt:session.createdAt,lastActiveAt:session.lastActiveAt,idleSeconds:Math.floor((now-session.lastActiveAt)/1e3)}}async function _getSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID;return sessions.get(sessionId)}async function _getOrCreateSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID,session=sessions.get(sessionId);return session?debug(`Reusing session with id ${sessionId}`):(debug(`No session could be found with id ${sessionId}`),session=_createSession(ctx,sessionId),sessions.set(sessionId,session)),session}function _scheduleIdleSessionCheck(){let noActiveSession=!1;setInterval(()=>{let currentTime=Date.now();noActiveSession&&sessions.size===0&&(info("No active session found, so terminating daemon server"),process.exit(0));for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>DAEMON_SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),_closeSession(session).then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}));noActiveSession=sessions.size===0},DAEMON_SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.json()}`)}async function startDaemonHTTPServer(port){let toolMap=Object.fromEntries(platformInfo.toolsInfo.tools.map(tool=>[tool.name(),tool]));app.use("*",cors({origin:"*",allowMethods:["GET","POST","DELETE","OPTIONS"],allowHeaders:["Content-Type","Authorization","session-id"]})),daemonPort=port,daemonStartTime=Date.now();let gracefulShutdown=async signal=>{info(`Received ${signal}, initiating graceful shutdown...`);let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));await Promise.allSettled(closePromises),info("All sessions closed, exiting..."),process.exit(0)};process.on("SIGTERM",()=>gracefulShutdown("SIGTERM")),process.on("SIGINT",()=>gracefulShutdown("SIGINT")),process.on("uncaughtException",err=>{error("Uncaught exception",err)}),process.on("unhandledRejection",reason=>{error("Unhandled rejection",reason)}),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/info",ctx=>{let info2={version:require2("../package.json").version,uptime:Math.floor((Date.now()-daemonStartTime)/1e3),sessionCount:sessions.size,port:daemonPort};return ctx.json(info2)}),app.get("/sessions",ctx=>{let sessionList=[];for(let session of sessions.values())sessionList.push(_getSessionInfo(session));return ctx.json({sessions:sessionList})}),app.get("/session",async ctx=>{let session=await _getSession(ctx);return session?ctx.json(_getSessionInfo(session)):ctx.json(ERRORS.sessionNotFound,404)}),app.post("/shutdown",async ctx=>{info("Shutdown request received, closing all sessions...");let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));return await Promise.allSettled(closePromises),info("All sessions closed, shutting down daemon server..."),setTimeout(()=>{process.exit(0)},500),ctx.json({status:"shutting_down"},200)}),app.post("/call",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let session=await _getOrCreateSession(ctx);session.lastActiveAt=Date.now();let toolCallRequest=await ctx.req.json(),tool=toolMap[toolCallRequest.toolName];if(!tool)return ctx.json(ERRORS.toolNotFound,404);let toolInput;try{toolInput=z.object(tool.inputSchema()).parse(toolCallRequest.toolInput)}catch(err){let errorMessage=err.errors&&Array.isArray(err.errors)?err.errors.map(e=>`${e.path?.join(".")||"input"}: ${e.message}`).join("; "):"Invalid tool input";return ctx.json(_buildErrorResponse(400,`Invalid Tool Request: ${errorMessage}`),400)}try{let toolCallResponse={toolOutput:await session.toolExecutor.executeTool(tool,toolInput)};return ctx.json(toolCallResponse,200)}catch(err){let toolCallResponse={toolError:{code:err.code,message:err.message}};return ctx.json(toolCallResponse,500)}}catch(err){return error("Error occurred while handling tool call request",err),ctx.json(ERRORS.internalServerError,500)}}),app.delete("/session",async ctx=>{try{let session=await _getSession(ctx);return session?(await _closeSession(session),ctx.json({ok:!0},200)):ctx.json(ERRORS.sessionNotFound,404)}catch(err){return error("Error occurred while deleting session",err),ctx.json(ERRORS.internalServerError,500)}}),app.onError((err,ctx)=>(error("Unhandled error in request handler",err),ctx.json({error:{code:500,message:"Internal Server Error"}},500))),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}var isMainModule=import.meta.url===`file://${process.argv[1]}`||import.meta.url===`file://${process.argv[1]}.mjs`||process.argv[1]?.endsWith("daemon-server.js")||process.argv[1]?.endsWith("daemon-server.mjs");if(isMainModule){let parsePort=function(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n};parsePort2=parsePort;let options=new Command().addOption(new Option("--port <number>","port for daemon HTTP server").argParser(parsePort).default(DAEMON_PORT)).allowUnknownOption().parse(process.argv).opts();enable(),info("Starting daemon HTTP server..."),startDaemonHTTPServer(options.port).then(()=>{info("Daemon HTTP server started")}).catch(err=>{error("Failed to start daemon HTTP server",err),process.exit(1)})}var parsePort2;export{startDaemonHTTPServer};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{platformInfo}from"./core-
|
|
2
|
+
import{platformInfo}from"./core-74IS3ZCO.js";import{PORT,SESSION_CLOSE_ON_SOCKET_CLOSE,SESSION_IDLE_CHECK_SECONDS,SESSION_IDLE_SECONDS,debug,disable,enable,error,info,isDebugEnabled}from"./core-22BVGANX.js";import{createRequire}from"node:module";var require2=createRequire(import.meta.url),SERVER_NAME="browser-devtools-mcp",SERVER_VERSION=require2("../package.json").version;function getServerInstructions(){let parts=[];return parts.push(platformInfo.serverInfo.instructions),parts.join(`
|
|
3
3
|
|
|
4
4
|
`).trim()}function getServerPolicies(){return platformInfo.serverInfo.policies}import crypto from"node:crypto";import{StreamableHTTPTransport}from"@hono/mcp";import{serve}from"@hono/node-server";import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{Hono}from"hono";import{cors}from"hono/cors";var MCP_TEMPLATE={jsonrpc:"2.0",error:{code:0,message:"N/A"},id:null},MCP_ERRORS={get sessionNotFound(){return _buildMCPErrorResponse(-32001,"Session Not Found")},get unauthorized(){return _buildMCPErrorResponse(-32001,"Unauthorized")},get internalServerError(){return _buildMCPErrorResponse(-32603,"Internal Server Error")}},sessions=new Map;function _buildMCPErrorResponse(code,message){let result={...MCP_TEMPLATE};return result.error.code=code,result.error.message=message,result}function _getImage(response){if("image"in response&&response.image!==null&&typeof response.image=="object"&&"data"in response.image&&"mimeType"in response.image&&Buffer.isBuffer(response.image.data)&&typeof response.image.mimeType=="string"){let image=response.image;return delete response.image,image}}function _toResponse(response){let image=_getImage(response),contents=[];return contents.push({type:"text",text:JSON.stringify(response,null,2)}),image&&(image.mimeType==="image/svg+xml"?contents.push({type:"text",text:image.data.toString(),mimeType:image.mimeType}):contents.push({type:"image",data:image.data.toString("base64"),mimeType:image.mimeType})),{content:contents,structuredContent:response,isError:!1}}function _createServer(opts){let server=new McpServer({name:SERVER_NAME,version:SERVER_VERSION},{capabilities:{resources:{},tools:{}},instructions:getServerInstructions()}),messages=[],policies=getServerPolicies();if(policies)for(let policy of policies)messages.push({role:"user",content:{type:"text",text:policy}});server.registerPrompt("default_system",{title:"Default System Prompt",description:"General behavior for the AI assistant"},async()=>({description:"Defines the assistant's general reasoning and tool usage rules.",messages}));let toolExecutor=platformInfo.toolsInfo.createToolExecutor(()=>opts.sessionIdProvider?opts.sessionIdProvider():""),createToolCallback=tool=>async args=>{try{let response=await toolExecutor.executeTool(tool,args);return _toResponse(response)}catch(error2){return{content:[{type:"text",text:`Error: ${error2.message}`}],isError:!0}}};return platformInfo.toolsInfo.tools.forEach(t=>{debug(`Registering tool ${t.name()} ...`),server.registerTool(t.name(),{description:t.description(),inputSchema:t.inputSchema(),outputSchema:t.outputSchema()},createToolCallback(t))}),server}async function _createAndConnectServer(transport,opts){let server=_createServer({config:opts.config,sessionIdProvider:()=>transport.sessionId});return await server.connect(transport),server}function _getConfig(){return{}}function _createSession(ctx,transport,server){let session={transport,server,closed:!1,lastActiveAt:Date.now()},socket=ctx.env.incoming.socket;return socket._mcpRegistered||(socket._mcpRegistered=!0,socket.on("close",async()=>{debug(`Socket, which is for MCP session with id ${transport.sessionId}, has been closed`),SESSION_CLOSE_ON_SOCKET_CLOSE&&await transport.close()})),_registerMCPSessionClose(transport,session.server),debug(`Created MCP server session with id ${transport.sessionId}`),session}async function _createTransport(ctx){let serverConfig=_getConfig(),holder={},transport=new StreamableHTTPTransport({enableJsonResponse:!0,sessionIdGenerator:()=>crypto.randomUUID(),onsessioninitialized:async sessionId=>{let session=_createSession(ctx,transport,holder.server);sessions.set(sessionId,session),debug(`MCP session initialized with id ${sessionId}`)},onsessionclosed:async sessionId=>{debug(`Closing MCP session closed with id ${sessionId} ...`),await transport.close(),debug(`MCP session closed with id ${sessionId}`)}});return holder.server=await _createAndConnectServer(transport,{config:serverConfig}),transport}async function _getTransport(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport}}async function _getOrCreateTransport(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport;debug(`No MCP session could be found with id ${sessionId}`);return}return await _createTransport(ctx)}function _registerMCPSessionClose(transport,mcpServer){let closed=!1;transport.onclose=async()=>{if(debug(`Closing MCP session with id ${transport.sessionId} ...`),closed){debug(`MCP session with id ${transport.sessionId} has already been closed`);return}closed=!0;try{await mcpServer.close(),debug("Closed MCP server")}catch(err){error("Error occurred while closing MCP server",err)}if(transport.sessionId){let session=sessions.get(transport.sessionId);if(session&&(session.closed=!0,session.context))try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(transport.sessionId)}debug(`Closing MCP session with id ${transport.sessionId} ...`)}}function _scheduleIdleSessionCheck(){setInterval(()=>{let currentTime=Date.now();for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),session.transport.close().then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}))},SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.json()}`)}function _markSessionAsActive(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);session&&(session.lastActiveAt=Date.now())}}async function startStdioServer(){let transport=new StdioServerTransport;await _createAndConnectServer(transport,{config:_getConfig()})}var app=new Hono;async function startStreamableHTTPServer(port){app.use("*",cors({origin:"*",allowMethods:["GET","POST","OPTIONS"],allowHeaders:["Content-Type","Authorization","MCP-Protocol-Version"]})),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/ping",ctx=>ctx.json({status:"ok",message:"pong"})),app.get("/mcp",ctx=>ctx.json({status:"ok",protocol:"model-context-protocol",version:"1.0"})),app.post("/mcp",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let transport=await _getOrCreateTransport(ctx);return transport?(_markSessionAsActive(ctx),await transport.handleRequest(ctx)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while handling MCP request",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.delete("/mcp",async ctx=>{try{let transport=await _getTransport(ctx);return transport?(await transport.close(),ctx.json({ok:!0},200)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while deleting MCP session",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}import{Command,Option,InvalidOptionArgumentError}from"commander";function _parsePort(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n}function _getOptions(){return new Command().addOption(new Option("--transport <type>","transport type").choices(["stdio","streamable-http"]).default("stdio")).addOption(new Option("--port <number>","port for Streamable HTTP transport").argParser(_parsePort).default(PORT)).allowUnknownOption().parse(process.argv).opts()}async function main(){let options=_getOptions();options.transport==="stdio"?(disable(),await startStdioServer()):options.transport==="streamable-http"?(info("Starting MCP server..."),await startStreamableHTTPServer(options.port),info("Started MCP Server")):(error(`Invalid transport: ${options.transport}`),process.exit(1))}main().catch(err=>{enable(),error("MCP server error",err),process.exit(1)});
|