browser-devtools-mcp 0.6.1 → 0.6.2

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{isToolEnabled,platformInfo}from"../core-YPBKINC4.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-S5JHUB3Z.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 schemaFieldToCliFlag(fieldName){return _toKebabCase(fieldName)}function _createOptionsForField(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodBoolean){if(defaultValue===!0){let positive=new Option(`--${flagName}`,description);positive.default(!0);let negative=new Option(`--no-${flagName}`,`Disable: ${description}`);return[positive,negative]}let boolOpt=new Option(`--${flagName}`,description);return defaultValue===!1&&boolOpt.default(!1),!isOptional&&defaultValue===void 0&&boolOpt.makeOptionMandatory(!0),[boolOpt]}let 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.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;if(typeof literalValue=="boolean"){if(literalValue===!0){let positive=new Option(`--${flagName}`,description);positive.default(!0);let negative=new Option(`--no-${flagName}`,`Disable: ${description}`);return[positive,negative]}let litFalse=new Option(`--${flagName}`,description);return litFalse.default(!1),[litFalse]}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,_toolName){let options=[];for(let[name,zodType]of Object.entries(schema))name!=="_metadata"&&options.push(..._createOptionsForField(name,zodType));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(),tool.name());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{existsSync,readFileSync}from"node:fs";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);function _resolveDaemonServerScriptPath(){let dir=__dirname;for(let i=0;i<10;i++){let candidate=path.join(dir,"daemon-server.js");if(existsSync(candidate))return candidate;let parent=path.dirname(dir);if(parent===dir)break;dir=parent}throw new Error("Could not find daemon-server.js (rebuild with npm run build or reinstall the package).")}var cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools.filter(isToolEnabled),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=_resolveDaemonServerScriptPath(),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(resolve2=>setTimeout(resolve2,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(()=>({}));_debug("Tool call error:",errorBody);let message=errorBody?.toolError?.message||errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`;throw new Error(message)}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{isToolEnabled,platformInfo}from"../core-72EHCUIW.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-S5JHUB3Z.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 schemaFieldToCliFlag(fieldName){return _toKebabCase(fieldName)}function _createOptionsForField(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodBoolean){if(defaultValue===!0){let positive=new Option(`--${flagName}`,description);positive.default(!0);let negative=new Option(`--no-${flagName}`,`Disable: ${description}`);return[positive,negative]}let boolOpt=new Option(`--${flagName}`,description);return defaultValue===!1&&boolOpt.default(!1),!isOptional&&defaultValue===void 0&&boolOpt.makeOptionMandatory(!0),[boolOpt]}let 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.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;if(typeof literalValue=="boolean"){if(literalValue===!0){let positive=new Option(`--${flagName}`,description);positive.default(!0);let negative=new Option(`--no-${flagName}`,`Disable: ${description}`);return[positive,negative]}let litFalse=new Option(`--${flagName}`,description);return litFalse.default(!1),[litFalse]}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,_toolName){let options=[];for(let[name,zodType]of Object.entries(schema))name!=="_metadata"&&options.push(..._createOptionsForField(name,zodType));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(),tool.name());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{existsSync,readFileSync}from"node:fs";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);function _resolveDaemonServerScriptPath(){let dir=__dirname;for(let i=0;i<10;i++){let candidate=path.join(dir,"daemon-server.js");if(existsSync(candidate))return candidate;let parent=path.dirname(dir);if(parent===dir)break;dir=parent}throw new Error("Could not find daemon-server.js (rebuild with npm run build or reinstall the package).")}var cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools.filter(isToolEnabled),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=_resolveDaemonServerScriptPath(),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(resolve2=>setTimeout(resolve2,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(()=>({}));_debug("Tool call error:",errorBody);let message=errorBody?.toolError?.message||errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`;throw new Error(message)}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(`
@@ -7,7 +7,7 @@ Max recursion depth: ${MAX_RECURSION_DEPTH}.
7
7
  \u2192 ${R_DISCOVERY_REMOTE}`)}function cdpConnectError(endpointSummary,cause,chromeRunning,openedInspectPage){let hint=chromeRunning===void 0?R_CONNECT_GENERIC:cdpConnectAttachHint(chromeRunning,openedInspectPage??!1);return new Error(`[CDP connect] ${cause}
8
8
  \u2192 endpoint: ${endpointSummary}
9
9
  \u2192 ${hint}`)}import net from"node:net";import{execSync}from"node:child_process";function hostPortFromCdpConnectRef(urlStr){let raw=typeof urlStr=="string"?urlStr.trim():"";if(!raw)throw cdpDiscoveryError("Connect URL is empty after endpoint resolution.");let withScheme=raw.startsWith("ws:")||raw.startsWith("wss:")?`http${raw.slice(raw.indexOf(":"))}`:raw.startsWith("http")?raw:`http://${raw}`,u;try{u=new URL(withScheme)}catch{throw cdpDiscoveryError(`Invalid URL "${raw.slice(0,120)}". Expected http://host:port or ws://host:port/...`)}let port=u.port?parseInt(u.port,10):u.protocol==="https:"?443:80;return{host:u.hostname,port}}function isLoopbackHost(host){let h=host.toLowerCase();return h==="localhost"||h==="127.0.0.1"||h==="::1"||h==="[::1]"}function isChromeRunning(){try{return process.platform==="win32"?execSync('tasklist /FI "IMAGENAME eq chrome.exe" /NH',{stdio:"pipe",encoding:"utf8"}).toLowerCase().includes("chrome.exe"):execSync("pgrep -x 'Google Chrome' || pgrep -x chrome || pgrep -x chromium || true",{stdio:"pipe",encoding:"utf8"}).trim().length>0}catch{return!1}}function openChromeRemoteDebuggingPage(){let url="chrome://inspect/#remote-debugging";try{if(process.platform==="darwin")execSync(`open -a "Google Chrome" "${url}"`,{stdio:"pipe",timeout:5e3});else if(process.platform==="win32")execSync(`start chrome "${url}"`,{stdio:"pipe",timeout:5e3});else try{execSync("google-chrome chrome://inspect/#remote-debugging",{stdio:"pipe",timeout:5e3})}catch{execSync("chromium chrome://inspect/#remote-debugging",{stdio:"pipe",timeout:5e3})}return!0}catch{return!1}}var DEFAULT_TIMEOUT_MS=5e3,CDP_PROBE_PORTS=[9222,9229];function _bracketIpv6(host){return host.includes(":")&&!host.startsWith("[")?`[${host}]`:host}function _rewriteWsHost(wsUrl,host,port){try{let u=new URL(wsUrl);return u.hostname=host.includes(":")?_bracketIpv6(host):host,u.port=String(port),u.toString()}catch{return wsUrl}}function _parseHttpCdpRoot(urlStr){try{let u=new URL(urlStr);if(u.protocol!=="http:"&&u.protocol!=="https:")return null;let port=u.port?parseInt(u.port,10):u.protocol==="https:"?443:80;return{host:u.hostname,port}}catch{return null}}async function _fetchText(url,timeoutMs){let ctrl=new AbortController,id=setTimeout(()=>{ctrl.abort()},timeoutMs);try{return await(await fetch(url,{signal:ctrl.signal})).text()}finally{clearTimeout(id)}}function _browserWsUrl(host,port){return`ws://${_bracketIpv6(host)}:${port}/devtools/browser`}function _tcpPortOpen(host,port,timeoutMs){return new Promise(resolve=>{let settled=!1,done=ok=>{settled||(settled=!0,clearTimeout(timer),resolve(ok))},sock=net.createConnection({host,port},()=>{sock.end(),done(!0)});sock.on("error",()=>{done(!1)});let timer=setTimeout(()=>{sock.destroy(),done(!1)},timeoutMs)})}async function _httpDiscoverWsUrl(host,port,timeoutMs){let b=_bracketIpv6(host);try{let body=await _fetchText(`http://${b}:${port}/json/version`,timeoutMs),info=JSON.parse(body);if(info.webSocketDebuggerUrl)return _rewriteWsHost(info.webSocketDebuggerUrl,host,port)}catch{}try{let body=await _fetchText(`http://${b}:${port}/json/list`,timeoutMs),targets=JSON.parse(body),ws=(targets.find(t=>t.type==="browser")??targets[0])?.webSocketDebuggerUrl;if(ws)return _rewriteWsHost(ws,host,port)}catch{}return null}async function _connectUrlForHostPort(host,port,timeoutMs=DEFAULT_TIMEOUT_MS){let fromHttp=await _httpDiscoverWsUrl(host,port,timeoutMs);return fromHttp||_browserWsUrl(host,port)}async function _tryLoopbackProbePorts(timeoutMs=DEFAULT_TIMEOUT_MS){let host="127.0.0.1";for(let port of CDP_PROBE_PORTS){let fromHttp=await _httpDiscoverWsUrl(host,port,timeoutMs);if(fromHttp)return{connectUrl:fromHttp,port}}for(let port of CDP_PROBE_PORTS)if(await _tcpPortOpen(host,port,timeoutMs))return{connectUrl:_browserWsUrl(host,port),port};return null}async function resolveCdpConnectEndpoint(options){let configuredHttpOrWsUrl=options.configuredHttpOrWsUrl;if(!options.explicitEndpointUrl){let found=await _tryLoopbackProbePorts();if(found)return{connectUrl:found.connectUrl,cacheKey:`cdp:127.0.0.1:${found.port}`};throw new CdpDiscoveryFailedError("No CDP on 127.0.0.1:9222 or :9229.",!0)}let url=configuredHttpOrWsUrl;if(url.startsWith("ws://")||url.startsWith("wss://"))return{connectUrl:url,cacheKey:url};let parsed=_parseHttpCdpRoot(url);if(!parsed)throw cdpDiscoveryError(`BROWSER_CDP_ENDPOINT_URL must be http(s)://host:port or ws(s)://\u2026, got: ${configuredHttpOrWsUrl}`);try{return{connectUrl:await _connectUrlForHostPort(parsed.host,parsed.port),cacheKey:url}}catch(e){let detail=e instanceof Error?e.message:String(e),line=`No DevTools at ${parsed.host}:${parsed.port}. ${detail}`;throw isLoopbackHost(parsed.host)?new CdpDiscoveryFailedError(line,!0):cdpDiscoveryError(line)}}import fs2 from"node:fs";import{chromium,firefox,webkit}from"playwright";var DEFAULT_BROWSER_TYPE="chromium",browsers=new Map,persistenceBrowserContexts=new Map,cdpByEndpoint=new Map;function _browserKey(browserOptions){return JSON.stringify(browserOptions)}function _browserLaunchOptions(browserOptions){let launchOptions={headless:browserOptions.headless,executablePath:browserOptions.executablePath,handleSIGINT:!1,handleSIGTERM:!1};if(browserOptions.useInstalledOnSystem)if(browserOptions.browserType==="chromium")launchOptions.channel="chrome",launchOptions.args=["--disable-blink-features=AutomationControlled"],launchOptions.ignoreDefaultArgs=["--disable-extensions"];else throw new Error(`Browser type ${browserOptions.browserType} is not supported to be used from the one installed on the system`);return launchOptions}async function _createBrowser(browserOptions){let browserInstance;switch(browserOptions.browserType){case"firefox":browserInstance=firefox;break;case"webkit":browserInstance=webkit;break;default:browserInstance=chromium;break}return browserInstance.launch(_browserLaunchOptions(browserOptions))}async function _getBrowser(browserOptions){let browserKey=_browserKey(browserOptions),browserInstance=browsers.get(browserKey);if(browserInstance&&!browserInstance.isConnected()){try{await browserInstance.close().catch(()=>{})}catch{}browserInstance=void 0}return browserInstance||(browserInstance=await _createBrowser(browserOptions),browsers.set(browserKey,browserInstance)),browserInstance}function _persistentBrowserContextKey(browserContextOptions){return browserContextOptions.persistent.userDataDir}function _persistentBrowserContextLaunchOptions(browserContextOptions){let browserOptions=browserContextOptions.browserOptions,launchOptions={headless:browserOptions.headless,executablePath:browserOptions.executablePath,bypassCSP:!0,viewport:browserOptions.headless?void 0:null,locale:browserContextOptions.locale};if(browserOptions.useInstalledOnSystem)if(browserOptions.browserType==="chromium")launchOptions.channel="chrome",launchOptions.args=["--disable-blink-features=AutomationControlled"],launchOptions.ignoreDefaultArgs=["--disable-extensions"];else throw new Error(`Browser type ${browserOptions.browserType} is not supported to be used from the one installed on the system`);return launchOptions}async function _createPersistentBrowserContext(browserContextOptions){let browserInstance;switch(browserContextOptions.browserOptions.browserType){case"firefox":browserInstance=firefox;break;case"webkit":browserInstance=webkit;break;default:browserInstance=chromium;break}let userDataDir=browserContextOptions.persistent.userDataDir;fs2.mkdirSync(userDataDir,{recursive:!0});let browserContext=await browserInstance.launchPersistentContext(userDataDir,_persistentBrowserContextLaunchOptions(browserContextOptions));for(let p of browserContext.pages())try{await p.close()}catch{}return browserContext}async function _getPersistentBrowserContext(browserContextOptions){let persistentBrowserContextKey=_persistentBrowserContextKey(browserContextOptions),browserContext=persistenceBrowserContexts.get(persistentBrowserContextKey);if(browserContext&&!browserContext.browser()?.isConnected()){try{await browserContext.close().catch(()=>{})}catch{}browserContext=void 0}if(!browserContext)browserContext=await _createPersistentBrowserContext(browserContextOptions),persistenceBrowserContexts.set(persistentBrowserContextKey,browserContext);else throw new Error(`There is already active persistent browser context in the user data directory: ${browserContextOptions.persistent?.userDataDir}`);return browserContext}function _openInspectIfChromeRunningOnLoopback(endpointHint){if(!BROWSER_CDP_OPEN_INSPECT||!isChromeRunning())return!1;try{let{host}=hostPortFromCdpConnectRef(endpointHint);if(isLoopbackHost(host))return openChromeRemoteDebuggingPage()}catch{return openChromeRemoteDebuggingPage()}return!1}async function _createCdpBrowserContext(connectUrl){let browser=await chromium.connectOverCDP(connectUrl),contexts=browser.contexts();if(contexts.length===0){let opened=_openInspectIfChromeRunningOnLoopback(connectUrl),running=isChromeRunning();throw cdpConnectError(connectUrl,"Attached but browser reported zero contexts (close other debug clients or re-enable remote debugging).",running,opened)}return{browser,context:contexts[0]}}async function _getCdpBrowserContext(){let resolvedRef,resolvedCacheKey;try{let r=await resolveCdpConnectEndpoint({configuredHttpOrWsUrl:BROWSER_CDP_CONNECT_URL,explicitEndpointUrl:BROWSER_CDP_ENDPOINT_EXPLICIT});resolvedRef=r.connectUrl,resolvedCacheKey=r.cacheKey}catch(e){if(e instanceof CdpDiscoveryFailedError&&e.loopbackAttach){let running=isChromeRunning(),openedInspect=BROWSER_CDP_OPEN_INSPECT&&running&&openChromeRemoteDebuggingPage();throw new Error(`${e.message}
10
- \u2192 ${cdpDiscoveryAttachHint(running,openedInspect)}`)}throw e}let connectUrl=resolvedRef,entry=cdpByEndpoint.get(resolvedCacheKey);if(entry&&!entry.browser.isConnected()&&(cdpByEndpoint.delete(resolvedCacheKey),entry=void 0),!entry){try{entry=await _createCdpBrowserContext(connectUrl)}catch(e){if(e instanceof Error&&e.message.startsWith("[CDP "))throw e;let runningGen=isChromeRunning(),openedGen=_openInspectIfChromeRunningOnLoopback(connectUrl);throw cdpConnectError(connectUrl,e instanceof Error?e.message:String(e),runningGen,openedGen)}cdpByEndpoint.set(resolvedCacheKey,entry)}return{browserContext:entry.context,dontCloseBrowserContext:!0}}async function newBrowserContext(browserContextOptions={browserOptions:{browserType:DEFAULT_BROWSER_TYPE,headless:BROWSER_HEADLESS_ENABLE,executablePath:BROWSER_EXECUTABLE_PATH,useInstalledOnSystem:BROWSER_USE_INSTALLED_ON_SYSTEM},persistent:BROWSER_PERSISTENT_ENABLE?{userDataDir:BROWSER_PERSISTENT_USER_DATA_DIR}:void 0,locale:BROWSER_LOCALE}){if(BROWSER_CDP_CONNECT_URL){if(browserContextOptions.persistent)throw new Error("BROWSER_CDP_ENDPOINT_URL / BROWSER_CDP_ENABLE cannot be used with BROWSER_PERSISTENT_ENABLE.");if(browserContextOptions.browserOptions.browserType!=="chromium")throw new Error("CDP attach mode supports Chromium-based browsers only (Chrome/Edge).");return _getCdpBrowserContext()}return browserContextOptions.persistent?{browserContext:await _getPersistentBrowserContext(browserContextOptions)}:{browserContext:await(await _getBrowser(browserContextOptions.browserOptions)).newContext({viewport:browserContextOptions.browserOptions.headless?void 0:null,bypassCSP:!0,locale:browserContextOptions.locale})}}async function newPage(browserContext,pageOptions={}){return await browserContext.newPage()}async function closeBrowserContext(browserContext){await browserContext.close();let deleted=!1;for(let[key,val]of persistenceBrowserContexts.entries())browserContext===val&&(persistenceBrowserContexts.delete(key),deleted=!0);return deleted}init_logger();init_logger();init_logger();init_logger();function _normalizeBasePath(input){let p=input.trim();return p.startsWith("/")||(p="/"+p),p.endsWith("/")||(p=p+"/"),p}function _normalizeUpstreamBaseUrl(input){let u=input.trim();return u&&(u.endsWith("/")?u.slice(0,-1):u)}function _computeSuffixPath(fullUrl,basePath){try{let pathname=new URL(fullUrl).pathname;if(!pathname.startsWith(basePath))return null;let raw=pathname.slice(basePath.length);return raw?raw.startsWith("/")?raw:"/"+raw:null}catch{return null}}function _appendSuffixToUpstream(upstreamBaseUrl,suffixPath,originalUrl){try{let qs=new URL(originalUrl).search??"";return upstreamBaseUrl+suffixPath+qs}catch{return upstreamBaseUrl+suffixPath}}var OTELProxy=class{config;queue;workers;isRunning;isInstalled;metrics;constructor(config){let maxQueueSize=config.maxQueueSize??200,concurrency=config.concurrency??2,respondNoContent=config.respondNoContent??!0,normalizedLocalPath=_normalizeBasePath(config.localPath),normalizedUpstreamUrl=_normalizeUpstreamBaseUrl(config.upstreamUrl);this.config={...config,localPath:normalizedLocalPath,upstreamUrl:normalizedUpstreamUrl,maxQueueSize,concurrency,respondNoContent},this.queue=[],this.workers=[],this.isRunning=!1,this.isInstalled=!1,this.metrics={routedRequests:0,acceptedBatches:0,droppedBatches:0,forwardedBatches:0,failedBatches:0,inFlight:0,queueSize:0,lastError:null}}getMetrics(){return{...this.metrics,queueSize:this.queue.length}}async install(context){if(this.isInstalled)return;let basePath=this.config.localPath;if(!basePath.startsWith("/"))throw new Error('localPath must start with "/" (e.g. "/__mcp_otel/").');let pattern=`**${basePath}**`;await context.route(pattern,async route=>{await this._handleRoute(route)}),this.isInstalled=!0,this.isRunning||await this.start(),debug(`[otel-proxy] installed route pattern: ${pattern} (basePath=${basePath}, upstreamBase=${this.config.upstreamUrl})`)}async uninstall(context){if(!this.isInstalled)return;let pattern=`**${this.config.localPath}**`;try{await context.unroute(pattern)}catch{}this.isInstalled=!1,await this.stop()}async start(){if(this.isRunning)return;this.isRunning=!0;let workerCount=Math.max(1,this.config.concurrency);for(let i=0;i<workerCount;i++){let w=this._workerLoop(i);this.workers.push(w)}debug(`[otel-proxy] started with concurrency=${workerCount}, maxQueueSize=${this.config.maxQueueSize}`)}async stop(){if(this.isRunning){this.isRunning=!1,this.queue.length=0;try{await Promise.allSettled(this.workers)}finally{this.workers.length=0}debug("[otel-proxy] stopped")}}async _handleRoute(route){let req=route.request();if(this.metrics.routedRequests++,req.method().toUpperCase()==="OPTIONS"){await this._fulfillFast(route);return}if(this.config.shouldForward&&!this.config.shouldForward(req)){await this._fulfillFast(route);return}let requestUrl=req.url(),basePath=this.config.localPath,suffixPath=_computeSuffixPath(requestUrl,basePath);if(!suffixPath){await route.fallback();return}let upstreamFullUrl=_appendSuffixToUpstream(this.config.upstreamUrl,suffixPath,requestUrl),body=await req.postDataBuffer()??Buffer.alloc(0),contentType=req.headers()["content-type"]??"application/json",method=req.method(),headers={"content-type":contentType};if(this.config.upstreamHeaders)for(let[k,v]of Object.entries(this.config.upstreamHeaders))headers[k]=v;if(this.queue.length>=this.config.maxQueueSize){this.metrics.droppedBatches++,await this._fulfillFast(route),warn(`[otel-proxy] dropped batch (queue full: ${this.queue.length}/${this.config.maxQueueSize}) suffix=${suffixPath}`);return}let item={body,contentType,createdAtMs:Date.now(),upstreamUrl:upstreamFullUrl,method,headers};this.queue.push(item),this.metrics.acceptedBatches++,await this._fulfillFast(route)}async _fulfillFast(route){let status=this.config.respondNoContent?204:200;if(status===204){await route.fulfill({status});return}await route.fulfill({status,headers:{"content-type":"text/plain; charset=utf-8"},body:""})}async _workerLoop(workerIndex){for(;this.isRunning;){let item=this.queue.shift();if(!item){await this._sleep(25);continue}this.metrics.inFlight++;try{await this._forwardUpstream(item),this.metrics.forwardedBatches++}catch(e){this.metrics.failedBatches++;let msg=e instanceof Error?e.message:String(e);this.metrics.lastError=msg,warn(`[otel-proxy] worker=${workerIndex} forward failed: ${msg}`)}finally{this.metrics.inFlight--}}}async _forwardUpstream(item){let res=await fetch(item.upstreamUrl,{method:item.method,headers:item.headers,body:new Uint8Array(item.body)});if(res.status<200||res.status>=300){let text=await this._safeReadText(res);throw new Error(`upstream returned ${res.status} for ${item.upstreamUrl}: ${text}`)}}async _safeReadText(res){try{return(await res.text()).slice(0,500)}catch{return""}}async _sleep(ms){await new Promise(resolve=>{setTimeout(()=>resolve(),ms)})}};import*as fs3 from"node:fs";import*as path2 from"node:path";import{fileURLToPath}from"node:url";var __filename=fileURLToPath(import.meta.url),__dirname=path2.dirname(__filename),OTEL_PROXY_LOCAL_PATH="/__mcp_otel/",OTEL_BUNDLE_FILE_NAME="otel-initializer.bundle.js";function _getOtelAssetsDir(){return OTEL_ASSETS_DIR?OTEL_ASSETS_DIR:path2.join(__dirname,"platform","browser","otel")}function _getOTELExporterConfig(){let type=OTEL_EXPORTER_TYPE,httpUrl=OTEL_EXPORTER_HTTP_URL;if(type==="console")return{type:"console"};if(type==="none"&&!httpUrl)return{type:"none"};if(type==="otlp/http"||type==="otlp/http-json"||type==="otlp/http-protobuf"||!!httpUrl){if(!httpUrl)throw new Error(`OTEL exporter HTTP url must be set when OTEL exporter type is "${type}"`);return{type:"otlp/http",traceFormat:type==="otlp/http-protobuf"?"protobuf":"json",url:OTEL_PROXY_LOCAL_PATH,upstreamURL:httpUrl,headers:OTEL_EXPORTER_HTTP_HEADERS}}throw new Error(`Invalid OTEL exporter type ${OTEL_EXPORTER_TYPE}`)}function _getOTELInstrumentationConfig(){return{userInteractionEvents:OTEL_INSTRUMENTATION_USER_INTERACTION_EVENTS}}function _getOTELConfig(){return{serviceName:OTEL_SERVICE_NAME,serviceVersion:OTEL_SERVICE_VERSION,exporter:_getOTELExporterConfig(),instrumentation:_getOTELInstrumentationConfig(),debug:!1}}function _readBundleContent(assetDir,bundleFileName){let assetDirAbs=path2.isAbsolute(assetDir)?assetDir:path2.join(process.cwd(),assetDir),filePath=path2.join(assetDirAbs,bundleFileName);if(!fs3.existsSync(filePath))throw new Error(`OTEL bundle not found at: ${filePath}`);return fs3.readFileSync(filePath,"utf-8")}async function _applyConfigToPage(page,cfg){await page.evaluate(nextCfg=>{let g=globalThis;g.__MCP_DEVTOOLS__||(g.__MCP_DEVTOOLS__={});let tid=nextCfg.traceId;typeof tid=="string"&&tid.trim()?g.__MCP_TRACE_ID__=tid.trim():delete g.__MCP_TRACE_ID__;let ts=nextCfg.traceState;typeof ts=="string"&&ts.trim()?g.__MCP_TRACE_STATE__=ts.trim():delete g.__MCP_TRACE_STATE__,g.__mcpOtel&&typeof g.__mcpOtel.init=="function"?g.__mcpOtel.init(nextCfg):(g.__MCP_DEVTOOLS__.otelInitialized=!1,g.__MCP_DEVTOOLS__.otelInitError="__mcpOtel.init is not available while applying config")},cfg).catch(e=>{let msg=e instanceof Error?e.message:String(e);debug(`[otel-controller] applyConfigToPage failed (ignored): ${msg}`)})}function _installAutoSync(browserContext,getCfg){let perPageHandlers=new WeakMap,attachToPage=page=>{if(perPageHandlers.has(page))return;let onFrameNavigated=async frame=>{frame===page.mainFrame()&&await _applyConfigToPage(page,getCfg())};perPageHandlers.set(page,onFrameNavigated),page.on("framenavigated",onFrameNavigated)};for(let p of browserContext.pages())attachToPage(p);let onNewPage=p=>{attachToPage(p)};browserContext.on("page",onNewPage);let detach=()=>{try{browserContext.off("page",onNewPage)}catch{}for(let p of browserContext.pages()){let h=perPageHandlers.get(p);if(h)try{p.off("framenavigated",h)}catch{}}};return debug("[otel-controller] auto-sync installed (page+framenavigated)"),{detach}}var OTELController=class{browserContext;config;proxy;initialized=!1;autoSyncDetach;constructor(browserContext){this.browserContext=browserContext,this.config=_getOTELConfig()}async init(options){if(this.initialized){debug("[otel-controller] init skipped: BrowserContext already initialized");return}if(!options.traceId||!options.traceId.trim())throw new Error("[otel-controller] init requires a non-empty traceId");this.config.traceId=options.traceId,this.config.traceState=options.traceState;let assetDir=_getOtelAssetsDir();this.config.exporter.type==="otlp/http"&&(this.proxy=new OTELProxy({localPath:OTEL_PROXY_LOCAL_PATH,upstreamUrl:this.config.exporter.upstreamURL,upstreamHeaders:{...this.config.exporter.headers??{}}}),await this.proxy.install(this.browserContext)),debug(`[otel-controller] exporter=${this.config.exporter.type} localBase=${OTEL_PROXY_LOCAL_PATH}`+(this.config.exporter.type==="otlp/http"?` upstreamBase=${this.config.exporter.upstreamURL}`:""));let bundleContent=_readBundleContent(assetDir,OTEL_BUNDLE_FILE_NAME),sync=_installAutoSync(this.browserContext,()=>this.config);this.autoSyncDetach=sync.detach,await this.browserContext.addInitScript({content:bundleContent}),await this.browserContext.addInitScript(cfg=>{let g=globalThis;g.__MCP_DEVTOOLS__||(g.__MCP_DEVTOOLS__={});let tid=cfg.traceId;typeof tid=="string"&&tid.trim()?g.__MCP_TRACE_ID__=tid.trim():delete g.__MCP_TRACE_ID__;let ts=cfg.traceState;typeof ts=="string"&&ts.trim()?g.__MCP_TRACE_STATE__=ts.trim():delete g.__MCP_TRACE_STATE__,g.__mcpOtel&&typeof g.__mcpOtel.init=="function"?g.__mcpOtel.init(cfg):(g.__MCP_DEVTOOLS__.otelInitialized=!1,g.__MCP_DEVTOOLS__.otelInitError="__mcpOtel.init is not available (initializer bundle did not install)")},this.config),this.initialized=!0,debug("[otel-controller] init installed: bundle + config init scripts + auto-sync")}isOTELRequest(request){return new URL(request.url()).pathname.startsWith(OTEL_PROXY_LOCAL_PATH)}async isInitialized(page){return await page.evaluate(()=>globalThis.__MCP_DEVTOOLS__?.otelInitialized===!0)}async getInitError(page){return await page.evaluate(()=>{let v=globalThis.__MCP_DEVTOOLS__?.otelInitError;if(typeof v=="string"&&v.trim())return v})}async getTraceId(page){return await page.evaluate(()=>{let g=globalThis;if(g.__mcpOtel&&typeof g.__mcpOtel.getTraceId=="function"){let tid=g.__mcpOtel.getTraceId();if(typeof tid=="string"&&tid.trim())return tid}let fallback=g.__MCP_TRACE_ID__;if(typeof fallback=="string"&&fallback.trim())return fallback})}async getTraceState(page){return await page.evaluate(()=>{let g=globalThis;if(g.__mcpOtel&&typeof g.__mcpOtel.getTraceState=="function"){let ts=g.__mcpOtel.getTraceState();if(typeof ts=="string"&&ts.trim())return ts.trim()}let fallback=g.__MCP_TRACE_STATE__;if(typeof fallback=="string"&&fallback.trim())return fallback.trim()})}async setTraceId(page,traceId){let trimmed=traceId.trim();this.config.traceId=trimmed||void 0,await page.evaluate(tid=>{let g=globalThis;g.__mcpOtel&&typeof g.__mcpOtel.setTraceId=="function"?g.__mcpOtel.setTraceId(tid):tid.trim()?g.__MCP_TRACE_ID__=tid.trim():delete g.__MCP_TRACE_ID__},traceId)}async setTraceState(page,traceState){let trimmed=traceState.trim();this.config.traceState=trimmed||void 0,await page.evaluate(ts=>{let g=globalThis;g.__mcpOtel&&typeof g.__mcpOtel.setTraceState=="function"?g.__mcpOtel.setTraceState(ts):g.__MCP_TRACE_STATE__=ts.trim()||void 0},traceState)}async close(){if(this.autoSyncDetach){try{this.autoSyncDetach()}catch{}this.autoSyncDetach=void 0}this.proxy&&(await this.proxy.uninstall(this.browserContext),this.proxy=void 0)}};import crypto from"node:crypto";function newTraceId(){return crypto.randomBytes(16).toString("hex")}function normalizeTraceId(traceId){let cleaned=traceId.trim().toLowerCase();if(!(/^[0-9a-f]{32}$/.test(cleaned)&&cleaned!=="0".repeat(32)))throw new Error("trace id must be 32 lowercase hex chars (not all zeros)");return cleaned}var MAX_TRACE_STATE_LEN=512,MAX_TRACE_STATE_ITEMS=32,VALID_KEY_CHAR_RANGE="[_0-9a-z-*/]",VALID_KEY=`[a-z]${VALID_KEY_CHAR_RANGE}{0,255}`,VALID_VENDOR_KEY=`[a-z0-9]${VALID_KEY_CHAR_RANGE}{0,240}@[a-z]${VALID_KEY_CHAR_RANGE}{0,13}`,VALID_KEY_REGEX=new RegExp(`^(?:${VALID_KEY}|${VALID_VENDOR_KEY})$`),VALID_VALUE_BASE_REGEX=/^[ -~]{0,255}[!-~]$/;function validateTracestateKey(key){return VALID_KEY_REGEX.test(key)}function validateTracestateValue(value){return VALID_VALUE_BASE_REGEX.test(value)&&!/[,=]/.test(value)}function assertValidTracestateForSet(raw){let t=raw.trim();if(!t)return;if(t.length>MAX_TRACE_STATE_LEN)throw new Error(`traceState exceeds maximum length (${MAX_TRACE_STATE_LEN} characters, W3C tracestate).`);let parts=t.split(",");if(parts.length>MAX_TRACE_STATE_ITEMS)throw new Error(`traceState has too many list members (max ${MAX_TRACE_STATE_ITEMS}, W3C tracestate).`);for(let i=0;i<parts.length;i++){let listMember=parts[i].trim();if(!listMember)throw new Error("traceState contains an empty list member (check commas / spacing).");let eq=listMember.indexOf("=");if(eq<=0||eq===listMember.length-1)throw new Error(`traceState list member must be "key=value": invalid entry at position ${i+1}.`);let key=listMember.slice(0,eq),value=listMember.slice(eq+1);if(!validateTracestateKey(key)||!validateTracestateValue(value))throw new Error(`traceState has invalid key or value at list position ${i+1} (W3C tracestate).`)}}var HttpMethod=(HttpMethod3=>(HttpMethod3.GET="GET",HttpMethod3.POST="POST",HttpMethod3.PUT="PUT",HttpMethod3.PATCH="PATCH",HttpMethod3.DELETE="DELETE",HttpMethod3.HEAD="HEAD",HttpMethod3.OPTIONS="OPTIONS",HttpMethod3))(HttpMethod||{}),HttpResourceType=(HttpResourceType2=>(HttpResourceType2.DOCUMENT="document",HttpResourceType2.STYLESHEET="stylesheet",HttpResourceType2.IMAGE="image",HttpResourceType2.MEDIA="media",HttpResourceType2.FONT="font",HttpResourceType2.SCRIPT="script",HttpResourceType2.TEXTTRACK="texttrack",HttpResourceType2.XHR="xhr",HttpResourceType2.FETCH="fetch",HttpResourceType2.EVENTSOURCE="eventsource",HttpResourceType2.WEBSOCKET="websocket",HttpResourceType2.MANIFEST="manifest",HttpResourceType2.OTHER="other",HttpResourceType2))(HttpResourceType||{});init_logger();function getEnumKeyTuples(enumObj){let values=Object.keys(enumObj).filter(key=>isNaN(Number(key))).map(key=>enumObj[key]);if(values.length===0)throw new Error("Enum has no values");return values}function createEnumTransformer(enumObj,opts){let values=Object.keys(enumObj).filter(k=>isNaN(Number(k))).map(k=>enumObj[k]),caseInsensitive=opts?.caseInsensitive??!0,lookup=new Map(values.map(v=>[caseInsensitive?v.toLowerCase():v,v]));return value=>{let key=caseInsensitive?value.toLowerCase():value,found=lookup.get(key);if(found===void 0)throw new Error(`Invalid enum value: "${value}"`);return found}}function formattedTimeForFilename(date=new Date){let pad=n=>String(n).padStart(2,"0");return date.getFullYear()+pad(date.getMonth()+1)+pad(date.getDate())+"-"+pad(date.getHours())+pad(date.getMinutes())+pad(date.getSeconds())}import{createRequire}from"node:module";import path3 from"node:path";var require2=createRequire(import.meta.url),DEFAULT_QUALITY=90,DEFAULT_FALLBACK_SIZE=1280,DEFAULT_NAME="recording";function _resolveFfmpegPath(){let{registry}=require2("playwright-core/lib/server/registry/index"),ffmpegPath=registry.findExecutable("ffmpeg").executablePath();if(!ffmpegPath)throw new Error('ffmpeg not found. Run "npx playwright install ffmpeg" to install it.');return ffmpegPath}function _createVideoRecorder(ffmpegPath,options){let registryPath=require2.resolve("playwright-core/lib/server/registry/index"),videoRecorderPath=path3.resolve(registryPath,"../../videoRecorder.js"),{VideoRecorder}=require2(videoRecorderPath);return new VideoRecorder(ffmpegPath,options)}var ScreenRecorder=class{_cdpSession=null;_videoRecorder=null;_recording=!1;_filePath="";_pageCrashHandler=null;_page=null;isRecording(){return this._recording}async start(context,page,options){if(this._recording)throw new Error("Screen recorder is already recording");let quality=options.quality??DEFAULT_QUALITY,filename=`${options.name??DEFAULT_NAME}-${formattedTimeForFilename()}.webm`,outputFile=path3.join(options.outputDir,filename);this._cdpSession=await context.newCDPSession(page);let layoutMetrics=await this._cdpSession.send("Page.getLayoutMetrics"),rawWidth=layoutMetrics.cssVisualViewport?.clientWidth??layoutMetrics.visualViewport?.clientWidth??page.viewportSize()?.width??DEFAULT_FALLBACK_SIZE,rawHeight=layoutMetrics.cssVisualViewport?.clientHeight??layoutMetrics.visualViewport?.clientHeight??page.viewportSize()?.height??DEFAULT_FALLBACK_SIZE,width=Math.round(rawWidth/2)*2,height=Math.round(rawHeight/2)*2,ffmpegPath=_resolveFfmpegPath();this._videoRecorder=_createVideoRecorder(ffmpegPath,{width,height,outputFile}),this._cdpSession.on("Page.screencastFrame",async event=>{let buffer=Buffer.from(event.data,"base64"),timestamp=event.metadata.timestamp??Date.now()/1e3;this._videoRecorder?.writeFrame(buffer,timestamp);try{await this._cdpSession?.send("Page.screencastFrameAck",{sessionId:event.sessionId})}catch{}}),await this._cdpSession.send("Page.startScreencast",{format:"jpeg",quality,maxWidth:width,maxHeight:height}),this._filePath=outputFile,this._page=page,this._recording=!0,this._pageCrashHandler=()=>{this.stop().catch(()=>{})},page.on("crash",this._pageCrashHandler),debug(`screen-recorder: started recording ${width}x${height} \u2192 ${outputFile}`)}async stop(){if(!this._recording)return;this._recording=!1;let filePath=this._filePath;try{await this._cdpSession?.send("Page.stopScreencast")}catch{}try{await this._cdpSession?.detach()}catch{}try{await this._videoRecorder?.stop()}catch(err){debug(`screen-recorder: error stopping ffmpeg: ${err}`)}return this._page&&this._pageCrashHandler&&this._page.removeListener("crash",this._pageCrashHandler),this._cdpSession=null,this._videoRecorder=null,this._filePath="",this._page=null,this._pageCrashHandler=null,debug(`screen-recorder: stopped, saved to ${filePath}`),{filePath}}};var BrowserToolSessionContext=class _BrowserToolSessionContext{static STATIC_RESOURCE_TYPES=new Set(["image","stylesheet","font","media","script","texttrack","manifest"]);static STATIC_ASSET_EXT=/\.(js|mjs|cjs|map|css|woff2?|ttf|otf|eot|png|jpe?g|gif|webp|svg|ico|mp4|webm|mp3|wav|pdf)(\?|$)/i;_sessionId;options;otelController;consoleMessages=[];httpRequests=[];initialized=!1;closed=!1;traceId;traceState;_numOfInFlightRequests=0;_lastNetworkActivityTimestamp=0;_refMap={};_consoleSeq=0;_httpSeq=0;browserContext;_page;_screenRecorder=new ScreenRecorder;_ensurePagePromise=null;_scenarioDepth=0;get page(){return this._page}constructor(sessionId,browserContext,page,options){this._sessionId=sessionId,this.browserContext=browserContext,this._page=page,this.options=options,this.otelController=new OTELController(this.browserContext)}async ensureSessionPageOpen(){if(this.closed)throw new Error("Session context is already closed");if(this._page.isClosed()){if(this._ensurePagePromise){await this._ensurePagePromise;return}this._ensurePagePromise=(async()=>{debug(`Session ${this._sessionId}: page closed; opening new tab as session page.`),this._screenRecorder.isRecording()&&await this._screenRecorder.stop(),this._refMap={},this._numOfInFlightRequests=0,this._lastNetworkActivityTimestamp=0,this._page=await this.browserContext.newPage(),this._attachPageListeners(this._page)})();try{await this._ensurePagePromise}finally{this._ensurePagePromise=null}}}_attachPageListeners(page){let me=this;page.on("console",msg=>{me.consoleMessages.push(me._toConsoleMessage(msg,++me._consoleSeq)),me.consoleMessages.length>BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE&&me.consoleMessages.splice(0,me.consoleMessages.length-BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE)}),page.on("pageerror",err=>{me.consoleMessages.push(me._errorToConsoleMessage(err,++me._consoleSeq)),me.consoleMessages.length>BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE&&me.consoleMessages.splice(0,me.consoleMessages.length-BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE)}),page.on("request",async req=>{me.otelController.isOTELRequest(req)||(me._numOfInFlightRequests++,me._lastNetworkActivityTimestamp=Date.now())}),page.on("requestfinished",async req=>{me.otelController.isOTELRequest(req)||(me._numOfInFlightRequests--,me._lastNetworkActivityTimestamp=Date.now(),me.httpRequests.push(await me._toHttpRequest(req,++me._httpSeq)),me.httpRequests.length>BROWSER_HTTP_REQUESTS_BUFFER_SIZE&&me.httpRequests.splice(0,me.httpRequests.length-BROWSER_HTTP_REQUESTS_BUFFER_SIZE))}),page.on("requestfailed",async req=>{me.otelController.isOTELRequest(req)||(me._numOfInFlightRequests--,me._lastNetworkActivityTimestamp=Date.now(),me.httpRequests.push(await me._toHttpRequest(req,++me._httpSeq)),me.httpRequests.length>BROWSER_HTTP_REQUESTS_BUFFER_SIZE&&me.httpRequests.splice(0,me.httpRequests.length-BROWSER_HTTP_REQUESTS_BUFFER_SIZE))})}async init(){if(this.closed)throw new Error("Session context is already closed");if(this.initialized)throw new Error("Session context is already initialized");this._attachPageListeners(this._page),this.options.otelEnable&&(this.traceId=newTraceId(),await this.otelController.init({traceId:this.traceId,traceState:this.traceState})),this.initialized=!0}_toConsoleMessageLevelName(type){switch(type){case"assert":case"error":return"error";case"warning":return"warning";case"count":case"dir":case"dirxml":case"info":case"log":case"table":case"time":case"timeEnd":return"info";case"clear":case"debug":case"endGroup":case"profile":case"profileEnd":case"startGroup":case"startGroupCollapsed":case"trace":return"debug";default:return"info"}}_toConsoleMessage(message,sequenceNumber){let timestamp=Date.now(),levelName=this._toConsoleMessageLevelName(message.type()),levelCode=ConsoleMessageLevel[levelName].code;return{type:message.type(),text:message.text(),level:{name:levelName,code:levelCode},location:{url:message.location().url,lineNumber:message.location().lineNumber,columnNumber:message.location().columnNumber},timestamp,sequenceNumber}}_errorToConsoleMessage(error2,sequenceNumber){let timestamp=Date.now();return error2 instanceof Error?{type:"error",text:error2.message,level:{name:"error",code:3},timestamp,sequenceNumber}:{type:"error",text:String(error2),level:{name:"error",code:3},timestamp,sequenceNumber}}_isStaticResourceUrl(url){try{let pathname=new URL(url).pathname;return _BrowserToolSessionContext.STATIC_ASSET_EXT.test(pathname)}catch{return!1}}_isBodyLikelyPresent(status,method){return!(method==="HEAD"||method==="OPTIONS"||status===204||status===304||status>=300&&status<400)}async _safeReadResponseBody(res){try{let method=res.request().method(),status=res.status();return this._isBodyLikelyPresent(status,method)?(await res.body()).toString("utf-8"):void 0}catch{return}}async _toHttpRequest(req,sequenceNumber){let res=await req.response(),resourceType=req.resourceType(),skipResponseBody=_BrowserToolSessionContext.STATIC_RESOURCE_TYPES.has(resourceType)||this._isStaticResourceUrl(req.url());return{url:req.url(),method:req.method(),headers:req.headers(),body:req.postData()||void 0,resourceType,failure:req.failure()?.errorText,duration:req.timing().responseEnd,response:res?{status:res.status(),statusText:res.statusText(),headers:res.headers(),body:skipResponseBody?void 0:await this._safeReadResponseBody(res)}:void 0,ok:res?res.ok():!1,timestamp:Math.floor(req.timing().startTime),sequenceNumber}}numOfInFlightRequests(){return this._numOfInFlightRequests}lastNetworkActivityTimestamp(){return this._lastNetworkActivityTimestamp}sessionId(){return this._sessionId}async getTraceId(){return this.traceId}async getTraceState(){return this.traceState}async getTraceContextFromBrowser(){if(!this.options.otelEnable)return{traceId:this.traceId,traceState:this.traceState};let traceId=await this.otelController.getTraceId(this.page),traceState=await this.otelController.getTraceState(this.page);return{traceId,traceState}}async setTraceId(traceId){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");let normalized=normalizeTraceId(traceId);this.traceId=normalized,await this.otelController.setTraceId(this.page,normalized)}async setTraceState(traceState){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");let trimmed=traceState.trim();trimmed&&assertValidTracestateForSet(trimmed),this.traceState=trimmed||void 0,await this.otelController.setTraceState(this.page,traceState)}async clearTraceState(){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");this.traceState=void 0,await this.otelController.setTraceState(this.page,"")}async resetTraceId(){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");let traceId=newTraceId();this.traceId=traceId,await this.otelController.setTraceId(this.page,traceId)}async clearTraceId(){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");this.traceId=void 0,await this.otelController.setTraceId(this.page,"")}async useTraceContextIfOTELEnabled(args){this.options.otelEnable&&(typeof args.traceId=="string"&&(args.traceId.trim()?normalizeTraceId(args.traceId)!==this.traceId&&await this.setTraceId(args.traceId):await this.clearTraceId()),typeof args.traceState=="string"&&(args.traceState.trim()?args.traceState.trim()!==this.traceState&&await this.setTraceState(args.traceState):await this.clearTraceState()))}getConsoleMessages(){return this.consoleMessages}getHttpRequests(){return this.httpRequests}getRefMap(){return this._refMap}setRefMap(refs){this._refMap=refs}isRecording(){return this._screenRecorder.isRecording()}async startRecording(options){await this._screenRecorder.start(this.browserContext,this._page,options)}async stopRecording(){return this._screenRecorder.stop()}scenarioDepth(){return this._scenarioDepth}incrementScenarioDepth(){this._scenarioDepth++}decrementScenarioDepth(){this._scenarioDepth--}executionContext(){return{page:this.page}}async close(){if(this.closed)return!1;this._screenRecorder.isRecording()&&await this._screenRecorder.stop(),debug(`Closing OTEL controller of the session with id ${this._sessionId} ...`),await this.otelController.close();try{this.options.dontCloseBrowserContext?(debug(`Closing session page only (CDP attach) for session ${this._sessionId} ...`),await this.page.close({runBeforeUnload:!1}).catch(()=>{})):(debug(`Closing browser context of the session with id ${this._sessionId} ...`),await closeBrowserContext(this.browserContext))}catch(err){debug(`Error occurred while closing browser context of the session with id ${this._sessionId} ...`,err)}return this.consoleMessages.length=0,this.httpRequests.length=0,this._refMap={},this.closed=!0,!0}};init_logger();import{randomUUID}from"node:crypto";import{request as httpsRequest}from"node:https";import{request as httpRequest}from"node:http";var SEND_TIMEOUT_MS=3e3,EVENTS_PATH="/v1/events";async function sendToolCallToCollector(metadata,toolName,toolInput,result){if(!metadata?.sessionId||!metadata.projectName||!metadata.verificationId||!metadata.traceId)return;let collectorUrl=metadata.collectorUrl??COLLECTOR_URL,collectorApiKey=metadata.collectorApiKey??COLLECTOR_API_KEY;if(!collectorUrl||!collectorApiKey)return;let cleanToolInput=toolInput&&typeof toolInput=="object"&&!Array.isArray(toolInput)?{...toolInput,_metadata:void 0}:toolInput,event={id:randomUUID(),type:"tool_call",timestamp:Date.now(),session_id:metadata.sessionId,project_name:metadata.projectName,verification_id:metadata.verificationId,trace_id:metadata.traceId,tool_name:toolName,tool_input:cleanToolInput,tool_response:result.tool_response,duration:result.duration,...result.error?{error:result.error}:{},source:"browser-devtools-mcp"},body=JSON.stringify([event]);return new Promise(resolve=>{try{let url=new URL(EVENTS_PATH,collectorUrl),isHttps=url.protocol==="https:",doRequest=isHttps?httpsRequest:httpRequest,timeout=setTimeout(()=>{debug("collector: send timeout"),resolve()},SEND_TIMEOUT_MS),req=doRequest({hostname:url.hostname,port:url.port||(isHttps?443:80),path:url.pathname,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(body),"X-API-Key":collectorApiKey},timeout:SEND_TIMEOUT_MS},res=>{res.on("data",()=>{}),res.on("end",()=>{clearTimeout(timeout),resolve()}),res.on("close",()=>{clearTimeout(timeout),resolve()})});req.on("error",err=>{clearTimeout(timeout),debug(`collector: send error: ${err.message}`),resolve()}),req.on("timeout",()=>{clearTimeout(timeout),req.destroy(),debug("collector: request timeout"),resolve()}),req.write(body),req.end()}catch(err){debug(`collector: sendToolCallToCollector failed: ${err}`),resolve()}})}init_logger();import{request as httpRequest2}from"node:http";import{request as httpsRequest2}from"node:https";var PRESIGN_TIMEOUT_MS=3e3,UPLOAD_TIMEOUT_MS=5e3;async function uploadArtifact(collectorUrl,apiKey,artifactType,data,contentType,sessionId){try{let presigned=await _getPresignedUrl(collectorUrl,apiKey,artifactType,contentType,sessionId);return!presigned||!await _putToS3(presigned.url,data,contentType)?void 0:{key:presigned.key}}catch(err){debug(`artifact-uploader: upload failed: ${err}`);return}}function _getPresignedUrl(collectorUrl,apiKey,artifactType,contentType,sessionId){return new Promise(resolve=>{try{let url=new URL(`/v1/artifacts/${artifactType}/upload-url`,collectorUrl);url.searchParams.set("contentType",contentType),url.searchParams.set("sessionId",sessionId);let isHttps=url.protocol==="https:",doRequest=isHttps?httpsRequest2:httpRequest2,req,timeout=setTimeout(()=>{debug("artifact-uploader: presign timeout"),req?.destroy(),resolve(void 0)},PRESIGN_TIMEOUT_MS);req=doRequest({hostname:url.hostname,port:url.port||(isHttps?443:80),path:url.pathname+url.search,method:"GET",headers:{"X-API-Key":apiKey},timeout:PRESIGN_TIMEOUT_MS},res=>{let chunks=[];res.on("data",chunk=>{chunks.push(chunk)}),res.on("end",()=>{if(clearTimeout(timeout),res.statusCode!==200){debug(`artifact-uploader: presign returned ${res.statusCode}`),resolve(void 0);return}try{let body=JSON.parse(Buffer.concat(chunks).toString("utf-8"));typeof body.url=="string"&&typeof body.key=="string"?resolve({url:body.url,key:body.key}):(debug("artifact-uploader: presign response missing url/key"),resolve(void 0))}catch{debug("artifact-uploader: presign response parse error"),resolve(void 0)}}),res.on("close",()=>{clearTimeout(timeout)})}),req.on("error",err=>{clearTimeout(timeout),debug(`artifact-uploader: presign error: ${err.message}`),resolve(void 0)}),req.on("timeout",()=>{clearTimeout(timeout),req.destroy(),debug("artifact-uploader: presign request timeout"),resolve(void 0)}),req.end()}catch(err){debug(`artifact-uploader: presign failed: ${err}`),resolve(void 0)}})}function _putToS3(presignedUrl,data,contentType){return new Promise(resolve=>{try{let url=new URL(presignedUrl),isHttps=url.protocol==="https:",doRequest=isHttps?httpsRequest2:httpRequest2,req,timeout=setTimeout(()=>{debug("artifact-uploader: S3 upload timeout"),req?.destroy(),resolve(!1)},UPLOAD_TIMEOUT_MS);req=doRequest({hostname:url.hostname,port:url.port||(isHttps?443:80),path:url.pathname+url.search,method:"PUT",headers:{"Content-Type":contentType,"Content-Length":data.length},timeout:UPLOAD_TIMEOUT_MS},res=>{res.on("data",()=>{}),res.on("end",()=>{clearTimeout(timeout),res.statusCode&&res.statusCode>=200&&res.statusCode<300?resolve(!0):(debug(`artifact-uploader: S3 upload returned ${res.statusCode}`),resolve(!1))}),res.on("close",()=>{clearTimeout(timeout)})}),req.on("error",err=>{clearTimeout(timeout),debug(`artifact-uploader: S3 upload error: ${err.message}`),resolve(!1)}),req.on("timeout",()=>{clearTimeout(timeout),req.destroy(),debug("artifact-uploader: S3 upload request timeout"),resolve(!1)}),req.write(data),req.end()}catch(err){debug(`artifact-uploader: S3 upload failed: ${err}`),resolve(!1)}})}init_logger();import fs4 from"node:fs/promises";var BrowserToolExecutor=class{async executeTool(context,tool,args){debug(`Executing tool ${tool.name()} with input: ${toJson(args)}`);let startTime=Date.now();try{args._metadata&&await context.useTraceContextIfOTELEnabled({traceId:typeof args._metadata.traceId=="string"?args._metadata.traceId:void 0,traceState:typeof args._metadata.traceState=="string"?args._metadata.traceState:void 0}),await context.ensureSessionPageOpen();let result=await tool.handle(context,args);return debug(`Executed tool ${tool.name()} and got output: ${toJson(result)}`),this._uploadArtifactsAndReport(args._metadata,tool.name(),args,result,Date.now()-startTime),result}catch(err){throw debug(`Error occurred while executing ${tool.name()}: ${err}`),sendToolCallToCollector(args._metadata,tool.name(),args,{duration:Date.now()-startTime,error:err instanceof Error?err.message:String(err)}),err}}async _uploadArtifactsAndReport(metadata,toolName,toolInput,result,duration){let collectorUrl=metadata?.collectorUrl??COLLECTOR_URL,collectorApiKey=metadata?.collectorApiKey??COLLECTOR_API_KEY,sessionId=metadata?.sessionId??"",canUpload=!!(collectorUrl&&collectorApiKey&&sessionId),uploadedArtifacts=[];if(canUpload){if(result._artifacts&&result._artifacts.length>0){let uploads=result._artifacts.map(async artifact=>{try{let data=await fs4.readFile(artifact.filePath),uploaded=await uploadArtifact(collectorUrl,collectorApiKey,artifact.type,data,artifact.mimeType,sessionId);uploaded&&uploadedArtifacts.push({type:artifact.type,key:uploaded.key})}catch(err){debug(`artifact-upload: failed to read/upload ${artifact.filePath}: ${err}`)}});await Promise.all(uploads)}let imageHolder=result;if(imageHolder.image?.data&&Buffer.isBuffer(imageHolder.image.data)&&uploadedArtifacts.length===0){let uploaded=await uploadArtifact(collectorUrl,collectorApiKey,"IMAGE",imageHolder.image.data,imageHolder.image.mimeType,sessionId);uploaded&&uploadedArtifacts.push({type:"IMAGE",key:uploaded.key})}}let collectorResponse={...result,_artifacts:void 0,image:void 0};uploadedArtifacts.length>0&&(collectorResponse.artifacts=uploadedArtifacts),sendToolCallToCollector(metadata,toolName,toolInput,{duration,tool_response:collectorResponse})}};var INTERACTIVE_ROLES=new Set(["button","link","textbox","checkbox","radio","combobox","listbox","menuitem","menuitemcheckbox","menuitemradio","option","searchbox","slider","spinbutton","switch","tab","treeitem"]),CONTENT_ROLES=new Set(["heading","cell","gridcell","columnheader","rowheader","listitem","article","region","main","navigation"]),STRUCTURAL_ROLES=new Set(["generic","group","list","table","row","rowgroup","grid","treegrid","menu","menubar","toolbar","tablist","tree","directory","document","application","presentation","none"]),refCounter=0;function nextRef(){return`e${++refCounter}`}function buildSelectorDescriptor(role,name){if(name!==void 0&&name!==""){let escaped=JSON.stringify(name);return`getByRole('${role}', { name: ${escaped}, exact: true })`}return`getByRole('${role}')`}function createRoleNameTracker(){let counts=new Map,refsByKey=new Map;return{getKey(role,name){return`${role}:${name??""}`},getNextIndex(role,name){let key=this.getKey(role,name),current=counts.get(key)??0;return counts.set(key,current+1),current},trackRef(role,name,ref){let key=this.getKey(role,name),refs=refsByKey.get(key)??[];refs.push(ref),refsByKey.set(key,refs)},getDuplicateKeys(){let duplicates=new Set;for(let[key,refs]of refsByKey)refs.length>1&&duplicates.add(key);return duplicates}}}function removeNthFromNonDuplicates(refs,tracker){let duplicateKeys=tracker.getDuplicateKeys();for(let entry of Object.values(refs)){let key=tracker.getKey(entry.role,entry.name);duplicateKeys.has(key)||delete entry.nth}}function getIndentLevel(line){let m=line.match(/^(\s*)/);return m?Math.floor(m[1].length/2):0}function processLine(line,refs,options,tracker){let depth=getIndentLevel(line);if(options.maxDepth!==void 0&&depth>options.maxDepth)return null;let match=line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);if(!match)return options.interactiveOnly?null:line;let[,prefix,role,name,suffix]=match,roleLower=role.toLowerCase();if(role.startsWith("/"))return line;let isInteractive=INTERACTIVE_ROLES.has(roleLower),isContent=CONTENT_ROLES.has(roleLower),isStructural=STRUCTURAL_ROLES.has(roleLower);if(options.interactiveOnly&&!isInteractive||options.compact&&isStructural&&!name)return null;if(!(isInteractive||isContent&&name))return line;let ref=nextRef(),nth=tracker.getNextIndex(roleLower,name);tracker.trackRef(roleLower,name,ref);let entry={role:roleLower,name:name??void 0,selector:buildSelectorDescriptor(roleLower,name),nth};refs[ref]=entry;let enhanced=`${prefix}${role}`;return name&&(enhanced+=` "${name}"`),enhanced+=` [ref=${ref}]`,nth>0&&(enhanced+=` [nth=${nth}]`),suffix&&suffix.trim()&&(enhanced+=suffix),enhanced}function compactTree(tree){let lines=tree.split(`
10
+ \u2192 ${cdpDiscoveryAttachHint(running,openedInspect)}`)}throw e}let connectUrl=resolvedRef,entry=cdpByEndpoint.get(resolvedCacheKey);if(entry&&!entry.browser.isConnected()&&(cdpByEndpoint.delete(resolvedCacheKey),entry=void 0),!entry){try{entry=await _createCdpBrowserContext(connectUrl)}catch(e){if(e instanceof Error&&e.message.startsWith("[CDP "))throw e;let runningGen=isChromeRunning(),openedGen=_openInspectIfChromeRunningOnLoopback(connectUrl);throw cdpConnectError(connectUrl,e instanceof Error?e.message:String(e),runningGen,openedGen)}cdpByEndpoint.set(resolvedCacheKey,entry)}return{browserContext:entry.context,dontCloseBrowserContext:!0}}async function newBrowserContext(browserContextOptions={browserOptions:{browserType:DEFAULT_BROWSER_TYPE,headless:BROWSER_HEADLESS_ENABLE,executablePath:BROWSER_EXECUTABLE_PATH,useInstalledOnSystem:BROWSER_USE_INSTALLED_ON_SYSTEM},persistent:BROWSER_PERSISTENT_ENABLE?{userDataDir:BROWSER_PERSISTENT_USER_DATA_DIR}:void 0,locale:BROWSER_LOCALE}){if(BROWSER_CDP_CONNECT_URL){if(browserContextOptions.persistent)throw new Error("BROWSER_CDP_ENDPOINT_URL / BROWSER_CDP_ENABLE cannot be used with BROWSER_PERSISTENT_ENABLE.");if(browserContextOptions.browserOptions.browserType!=="chromium")throw new Error("CDP attach mode supports Chromium-based browsers only (Chrome/Edge).");return _getCdpBrowserContext()}return browserContextOptions.persistent?{browserContext:await _getPersistentBrowserContext(browserContextOptions)}:{browserContext:await(await _getBrowser(browserContextOptions.browserOptions)).newContext({viewport:browserContextOptions.browserOptions.headless?void 0:null,bypassCSP:!0,locale:browserContextOptions.locale})}}async function newPage(browserContext,pageOptions={}){return await browserContext.newPage()}async function closeBrowserContext(browserContext){await browserContext.close();let deleted=!1;for(let[key,val]of persistenceBrowserContexts.entries())browserContext===val&&(persistenceBrowserContexts.delete(key),deleted=!0);return deleted}init_logger();init_logger();init_logger();init_logger();function _normalizeBasePath(input){let p=input.trim();return p.startsWith("/")||(p="/"+p),p.endsWith("/")||(p=p+"/"),p}function _normalizeUpstreamBaseUrl(input){let u=input.trim();return u&&(u.endsWith("/")?u.slice(0,-1):u)}function _computeSuffixPath(fullUrl,basePath){try{let pathname=new URL(fullUrl).pathname;if(!pathname.startsWith(basePath))return null;let raw=pathname.slice(basePath.length);return raw?raw.startsWith("/")?raw:"/"+raw:null}catch{return null}}function _appendSuffixToUpstream(upstreamBaseUrl,suffixPath,originalUrl){try{let qs=new URL(originalUrl).search??"";return upstreamBaseUrl+suffixPath+qs}catch{return upstreamBaseUrl+suffixPath}}var OTELProxy=class{config;queue;workers;isRunning;isInstalled;metrics;constructor(config){let maxQueueSize=config.maxQueueSize??200,concurrency=config.concurrency??2,respondNoContent=config.respondNoContent??!0,normalizedLocalPath=_normalizeBasePath(config.localPath),normalizedUpstreamUrl=_normalizeUpstreamBaseUrl(config.upstreamUrl);this.config={...config,localPath:normalizedLocalPath,upstreamUrl:normalizedUpstreamUrl,maxQueueSize,concurrency,respondNoContent},this.queue=[],this.workers=[],this.isRunning=!1,this.isInstalled=!1,this.metrics={routedRequests:0,acceptedBatches:0,droppedBatches:0,forwardedBatches:0,failedBatches:0,inFlight:0,queueSize:0,lastError:null}}getMetrics(){return{...this.metrics,queueSize:this.queue.length}}async install(context){if(this.isInstalled)return;let basePath=this.config.localPath;if(!basePath.startsWith("/"))throw new Error('localPath must start with "/" (e.g. "/__mcp_otel/").');let pattern=`**${basePath}**`;await context.route(pattern,async route=>{await this._handleRoute(route)}),this.isInstalled=!0,this.isRunning||await this.start(),debug(`[otel-proxy] installed route pattern: ${pattern} (basePath=${basePath}, upstreamBase=${this.config.upstreamUrl})`)}async uninstall(context){if(!this.isInstalled)return;let pattern=`**${this.config.localPath}**`;try{await context.unroute(pattern)}catch{}this.isInstalled=!1,await this.stop()}async start(){if(this.isRunning)return;this.isRunning=!0;let workerCount=Math.max(1,this.config.concurrency);for(let i=0;i<workerCount;i++){let w=this._workerLoop(i);this.workers.push(w)}debug(`[otel-proxy] started with concurrency=${workerCount}, maxQueueSize=${this.config.maxQueueSize}`)}async stop(){if(this.isRunning){this.isRunning=!1,this.queue.length=0;try{await Promise.allSettled(this.workers)}finally{this.workers.length=0}debug("[otel-proxy] stopped")}}async _handleRoute(route){let req=route.request();if(this.metrics.routedRequests++,req.method().toUpperCase()==="OPTIONS"){await this._fulfillFast(route);return}if(this.config.shouldForward&&!this.config.shouldForward(req)){await this._fulfillFast(route);return}let requestUrl=req.url(),basePath=this.config.localPath,suffixPath=_computeSuffixPath(requestUrl,basePath);if(!suffixPath){await route.fallback();return}let upstreamFullUrl=_appendSuffixToUpstream(this.config.upstreamUrl,suffixPath,requestUrl),body=await req.postDataBuffer()??Buffer.alloc(0),contentType=req.headers()["content-type"]??"application/json",method=req.method(),headers={"content-type":contentType};if(this.config.upstreamHeaders)for(let[k,v]of Object.entries(this.config.upstreamHeaders))headers[k]=v;if(this.queue.length>=this.config.maxQueueSize){this.metrics.droppedBatches++,await this._fulfillFast(route),warn(`[otel-proxy] dropped batch (queue full: ${this.queue.length}/${this.config.maxQueueSize}) suffix=${suffixPath}`);return}let item={body,contentType,createdAtMs:Date.now(),upstreamUrl:upstreamFullUrl,method,headers};this.queue.push(item),this.metrics.acceptedBatches++,await this._fulfillFast(route)}async _fulfillFast(route){let status=this.config.respondNoContent?204:200;if(status===204){await route.fulfill({status});return}await route.fulfill({status,headers:{"content-type":"text/plain; charset=utf-8"},body:""})}async _workerLoop(workerIndex){for(;this.isRunning;){let item=this.queue.shift();if(!item){await this._sleep(25);continue}this.metrics.inFlight++;try{await this._forwardUpstream(item),this.metrics.forwardedBatches++}catch(e){this.metrics.failedBatches++;let msg=e instanceof Error?e.message:String(e);this.metrics.lastError=msg,warn(`[otel-proxy] worker=${workerIndex} forward failed: ${msg}`)}finally{this.metrics.inFlight--}}}async _forwardUpstream(item){let res=await fetch(item.upstreamUrl,{method:item.method,headers:item.headers,body:new Uint8Array(item.body)});if(res.status<200||res.status>=300){let text=await this._safeReadText(res);throw new Error(`upstream returned ${res.status} for ${item.upstreamUrl}: ${text}`)}}async _safeReadText(res){try{return(await res.text()).slice(0,500)}catch{return""}}async _sleep(ms){await new Promise(resolve=>{setTimeout(()=>resolve(),ms)})}};import*as fs3 from"node:fs";import*as path2 from"node:path";import{fileURLToPath}from"node:url";var __filename=fileURLToPath(import.meta.url),__dirname=path2.dirname(__filename),OTEL_PROXY_LOCAL_PATH="/__mcp_otel/",OTEL_BUNDLE_FILE_NAME="otel-initializer.bundle.js";function _getOtelAssetsDir(){return OTEL_ASSETS_DIR?OTEL_ASSETS_DIR:path2.join(__dirname,"platform","browser","otel")}function _getOTELExporterConfig(){let type=OTEL_EXPORTER_TYPE,httpUrl=OTEL_EXPORTER_HTTP_URL;if(type==="console")return{type:"console"};if(type==="none"&&!httpUrl)return{type:"none"};if(type==="otlp/http"||type==="otlp/http-json"||type==="otlp/http-protobuf"||!!httpUrl){if(!httpUrl)throw new Error(`OTEL exporter HTTP url must be set when OTEL exporter type is "${type}"`);return{type:"otlp/http",traceFormat:type==="otlp/http-protobuf"?"protobuf":"json",url:OTEL_PROXY_LOCAL_PATH,upstreamURL:httpUrl,headers:OTEL_EXPORTER_HTTP_HEADERS}}throw new Error(`Invalid OTEL exporter type ${OTEL_EXPORTER_TYPE}`)}function _getOTELInstrumentationConfig(){return{userInteractionEvents:OTEL_INSTRUMENTATION_USER_INTERACTION_EVENTS}}function _getOTELConfig(){return{serviceName:OTEL_SERVICE_NAME,serviceVersion:OTEL_SERVICE_VERSION,exporter:_getOTELExporterConfig(),instrumentation:_getOTELInstrumentationConfig(),debug:!1}}function _readBundleContent(assetDir,bundleFileName){let assetDirAbs=path2.isAbsolute(assetDir)?assetDir:path2.join(process.cwd(),assetDir),filePath=path2.join(assetDirAbs,bundleFileName);if(!fs3.existsSync(filePath))throw new Error(`OTEL bundle not found at: ${filePath}`);return fs3.readFileSync(filePath,"utf-8")}async function _applyConfigToPage(page,cfg){await page.evaluate(nextCfg=>{let g=globalThis;g.__MCP_DEVTOOLS__||(g.__MCP_DEVTOOLS__={});let tid=nextCfg.traceId;typeof tid=="string"&&tid.trim()?g.__MCP_TRACE_ID__=tid.trim():delete g.__MCP_TRACE_ID__;let ts=nextCfg.traceState;typeof ts=="string"&&ts.trim()?g.__MCP_TRACE_STATE__=ts.trim():delete g.__MCP_TRACE_STATE__,g.__mcpOtel&&typeof g.__mcpOtel.init=="function"?g.__mcpOtel.init(nextCfg):(g.__MCP_DEVTOOLS__.otelInitialized=!1,g.__MCP_DEVTOOLS__.otelInitError="__mcpOtel.init is not available while applying config")},cfg).catch(e=>{let msg=e instanceof Error?e.message:String(e);debug(`[otel-controller] applyConfigToPage failed (ignored): ${msg}`)})}function _installAutoSync(browserContext,getCfg){let perPageHandlers=new WeakMap,attachToPage=page=>{if(perPageHandlers.has(page))return;let onFrameNavigated=async frame=>{frame===page.mainFrame()&&await _applyConfigToPage(page,getCfg())};perPageHandlers.set(page,onFrameNavigated),page.on("framenavigated",onFrameNavigated)};for(let p of browserContext.pages())attachToPage(p);let onNewPage=p=>{attachToPage(p)};browserContext.on("page",onNewPage);let detach=()=>{try{browserContext.off("page",onNewPage)}catch{}for(let p of browserContext.pages()){let h=perPageHandlers.get(p);if(h)try{p.off("framenavigated",h)}catch{}}};return debug("[otel-controller] auto-sync installed (page+framenavigated)"),{detach}}var OTELController=class{browserContext;config;proxy;initialized=!1;autoSyncDetach;constructor(browserContext){this.browserContext=browserContext,this.config=_getOTELConfig()}async init(options){if(this.initialized){debug("[otel-controller] init skipped: BrowserContext already initialized");return}if(!options.traceId||!options.traceId.trim())throw new Error("[otel-controller] init requires a non-empty traceId");this.config.traceId=options.traceId,this.config.traceState=options.traceState;let assetDir=_getOtelAssetsDir();this.config.exporter.type==="otlp/http"&&(this.proxy=new OTELProxy({localPath:OTEL_PROXY_LOCAL_PATH,upstreamUrl:this.config.exporter.upstreamURL,upstreamHeaders:{...this.config.exporter.headers??{}}}),await this.proxy.install(this.browserContext)),debug(`[otel-controller] exporter=${this.config.exporter.type} localBase=${OTEL_PROXY_LOCAL_PATH}`+(this.config.exporter.type==="otlp/http"?` upstreamBase=${this.config.exporter.upstreamURL}`:""));let bundleContent=_readBundleContent(assetDir,OTEL_BUNDLE_FILE_NAME),sync=_installAutoSync(this.browserContext,()=>this.config);this.autoSyncDetach=sync.detach,await this.browserContext.addInitScript({content:bundleContent}),await this.browserContext.addInitScript(cfg=>{let g=globalThis;g.__MCP_DEVTOOLS__||(g.__MCP_DEVTOOLS__={});let tid=cfg.traceId;typeof tid=="string"&&tid.trim()?g.__MCP_TRACE_ID__=tid.trim():delete g.__MCP_TRACE_ID__;let ts=cfg.traceState;typeof ts=="string"&&ts.trim()?g.__MCP_TRACE_STATE__=ts.trim():delete g.__MCP_TRACE_STATE__,g.__mcpOtel&&typeof g.__mcpOtel.init=="function"?g.__mcpOtel.init(cfg):(g.__MCP_DEVTOOLS__.otelInitialized=!1,g.__MCP_DEVTOOLS__.otelInitError="__mcpOtel.init is not available (initializer bundle did not install)")},this.config),this.initialized=!0,debug("[otel-controller] init installed: bundle + config init scripts + auto-sync")}isOTELRequest(request){return new URL(request.url()).pathname.startsWith(OTEL_PROXY_LOCAL_PATH)}async isInitialized(page){return await page.evaluate(()=>globalThis.__MCP_DEVTOOLS__?.otelInitialized===!0)}async getInitError(page){return await page.evaluate(()=>{let v=globalThis.__MCP_DEVTOOLS__?.otelInitError;if(typeof v=="string"&&v.trim())return v})}async getTraceId(page){return await page.evaluate(()=>{let g=globalThis;if(g.__mcpOtel&&typeof g.__mcpOtel.getTraceId=="function"){let tid=g.__mcpOtel.getTraceId();if(typeof tid=="string"&&tid.trim())return tid}let fallback=g.__MCP_TRACE_ID__;if(typeof fallback=="string"&&fallback.trim())return fallback})}async getTraceState(page){return await page.evaluate(()=>{let g=globalThis;if(g.__mcpOtel&&typeof g.__mcpOtel.getTraceState=="function"){let ts=g.__mcpOtel.getTraceState();if(typeof ts=="string"&&ts.trim())return ts.trim()}let fallback=g.__MCP_TRACE_STATE__;if(typeof fallback=="string"&&fallback.trim())return fallback.trim()})}async setTraceId(page,traceId){let trimmed=traceId.trim();this.config.traceId=trimmed||void 0,await page.evaluate(tid=>{let g=globalThis;g.__mcpOtel&&typeof g.__mcpOtel.setTraceId=="function"?g.__mcpOtel.setTraceId(tid):tid.trim()?g.__MCP_TRACE_ID__=tid.trim():delete g.__MCP_TRACE_ID__},traceId)}async setTraceState(page,traceState){let trimmed=traceState.trim();this.config.traceState=trimmed||void 0,await page.evaluate(ts=>{let g=globalThis;g.__mcpOtel&&typeof g.__mcpOtel.setTraceState=="function"?g.__mcpOtel.setTraceState(ts):g.__MCP_TRACE_STATE__=ts.trim()||void 0},traceState)}async close(){if(this.autoSyncDetach){try{this.autoSyncDetach()}catch{}this.autoSyncDetach=void 0}this.proxy&&(await this.proxy.uninstall(this.browserContext),this.proxy=void 0)}};import crypto from"node:crypto";function newTraceId(){return crypto.randomBytes(16).toString("hex")}function normalizeTraceId(traceId){let cleaned=traceId.trim().toLowerCase();if(!(/^[0-9a-f]{32}$/.test(cleaned)&&cleaned!=="0".repeat(32)))throw new Error("trace id must be 32 lowercase hex chars (not all zeros)");return cleaned}var MAX_TRACE_STATE_LEN=512,MAX_TRACE_STATE_ITEMS=32,VALID_KEY_CHAR_RANGE="[_0-9a-z-*/]",VALID_KEY=`[a-z]${VALID_KEY_CHAR_RANGE}{0,255}`,VALID_VENDOR_KEY=`[a-z0-9]${VALID_KEY_CHAR_RANGE}{0,240}@[a-z]${VALID_KEY_CHAR_RANGE}{0,13}`,VALID_KEY_REGEX=new RegExp(`^(?:${VALID_KEY}|${VALID_VENDOR_KEY})$`),VALID_VALUE_BASE_REGEX=/^[ -~]{0,255}[!-~]$/;function validateTracestateKey(key){return VALID_KEY_REGEX.test(key)}function validateTracestateValue(value){return VALID_VALUE_BASE_REGEX.test(value)&&!/[,=]/.test(value)}function assertValidTracestateForSet(raw){let t=raw.trim();if(!t)return;if(t.length>MAX_TRACE_STATE_LEN)throw new Error(`traceState exceeds maximum length (${MAX_TRACE_STATE_LEN} characters, W3C tracestate).`);let parts=t.split(",");if(parts.length>MAX_TRACE_STATE_ITEMS)throw new Error(`traceState has too many list members (max ${MAX_TRACE_STATE_ITEMS}, W3C tracestate).`);for(let i=0;i<parts.length;i++){let listMember=parts[i].trim();if(!listMember)throw new Error("traceState contains an empty list member (check commas / spacing).");let eq=listMember.indexOf("=");if(eq<=0||eq===listMember.length-1)throw new Error(`traceState list member must be "key=value": invalid entry at position ${i+1}.`);let key=listMember.slice(0,eq),value=listMember.slice(eq+1);if(!validateTracestateKey(key)||!validateTracestateValue(value))throw new Error(`traceState has invalid key or value at list position ${i+1} (W3C tracestate).`)}}var HttpMethod=(HttpMethod3=>(HttpMethod3.GET="GET",HttpMethod3.POST="POST",HttpMethod3.PUT="PUT",HttpMethod3.PATCH="PATCH",HttpMethod3.DELETE="DELETE",HttpMethod3.HEAD="HEAD",HttpMethod3.OPTIONS="OPTIONS",HttpMethod3))(HttpMethod||{}),HttpResourceType=(HttpResourceType2=>(HttpResourceType2.DOCUMENT="document",HttpResourceType2.STYLESHEET="stylesheet",HttpResourceType2.IMAGE="image",HttpResourceType2.MEDIA="media",HttpResourceType2.FONT="font",HttpResourceType2.SCRIPT="script",HttpResourceType2.TEXTTRACK="texttrack",HttpResourceType2.XHR="xhr",HttpResourceType2.FETCH="fetch",HttpResourceType2.EVENTSOURCE="eventsource",HttpResourceType2.WEBSOCKET="websocket",HttpResourceType2.MANIFEST="manifest",HttpResourceType2.OTHER="other",HttpResourceType2))(HttpResourceType||{});init_logger();function getEnumKeyTuples(enumObj){let values=Object.keys(enumObj).filter(key=>isNaN(Number(key))).map(key=>enumObj[key]);if(values.length===0)throw new Error("Enum has no values");return values}function createEnumTransformer(enumObj,opts){let values=Object.keys(enumObj).filter(k=>isNaN(Number(k))).map(k=>enumObj[k]),caseInsensitive=opts?.caseInsensitive??!0,lookup=new Map(values.map(v=>[caseInsensitive?v.toLowerCase():v,v]));return value=>{let key=caseInsensitive?value.toLowerCase():value,found=lookup.get(key);if(found===void 0)throw new Error(`Invalid enum value: "${value}"`);return found}}function formattedTimeForFilename(date=new Date){let pad=n=>String(n).padStart(2,"0");return date.getFullYear()+pad(date.getMonth()+1)+pad(date.getDate())+"-"+pad(date.getHours())+pad(date.getMinutes())+pad(date.getSeconds())}import{createRequire}from"node:module";import path3 from"node:path";var require2=createRequire(import.meta.url),DEFAULT_QUALITY=90,DEFAULT_FALLBACK_SIZE=1280,DEFAULT_NAME="recording";function _resolveFfmpegPath(){let{registry}=require2("playwright-core/lib/server/registry/index"),ffmpegPath=registry.findExecutable("ffmpeg").executablePath();if(!ffmpegPath)throw new Error('ffmpeg not found. Run "npx playwright install ffmpeg" to install it.');return ffmpegPath}function _createVideoRecorder(ffmpegPath,options){let registryPath=require2.resolve("playwright-core/lib/server/registry/index"),videoRecorderPath=path3.resolve(registryPath,"../../videoRecorder.js"),{VideoRecorder}=require2(videoRecorderPath);return new VideoRecorder(ffmpegPath,options)}var ScreenRecorder=class{_cdpSession=null;_videoRecorder=null;_recording=!1;_filePath="";_pageCrashHandler=null;_page=null;isRecording(){return this._recording}async start(context,page,options){if(this._recording)throw new Error("Screen recorder is already recording");let quality=options.quality??DEFAULT_QUALITY,filename=`${options.name??DEFAULT_NAME}-${formattedTimeForFilename()}.webm`,outputFile=path3.join(options.outputDir,filename);this._cdpSession=await context.newCDPSession(page);let layoutMetrics=await this._cdpSession.send("Page.getLayoutMetrics"),rawWidth=layoutMetrics.cssVisualViewport?.clientWidth??layoutMetrics.visualViewport?.clientWidth??page.viewportSize()?.width??DEFAULT_FALLBACK_SIZE,rawHeight=layoutMetrics.cssVisualViewport?.clientHeight??layoutMetrics.visualViewport?.clientHeight??page.viewportSize()?.height??DEFAULT_FALLBACK_SIZE,width=Math.round(rawWidth/2)*2,height=Math.round(rawHeight/2)*2,ffmpegPath=_resolveFfmpegPath();this._videoRecorder=_createVideoRecorder(ffmpegPath,{width,height,outputFile}),this._cdpSession.on("Page.screencastFrame",async event=>{let buffer=Buffer.from(event.data,"base64"),timestamp=event.metadata.timestamp??Date.now()/1e3;try{this._videoRecorder?.writeFrame(buffer,timestamp)}catch(err){debug(`screen-recorder: error writing frame: ${err}`)}try{await this._cdpSession?.send("Page.screencastFrameAck",{sessionId:event.sessionId})}catch{}}),await this._cdpSession.send("Page.startScreencast",{format:"jpeg",quality,maxWidth:width,maxHeight:height}),this._filePath=outputFile,this._page=page,this._recording=!0,this._pageCrashHandler=()=>{this.stop().catch(()=>{})},page.on("crash",this._pageCrashHandler),debug(`screen-recorder: started recording ${width}x${height} \u2192 ${outputFile}`)}async stop(){if(!this._recording)return;this._recording=!1;let filePath=this._filePath;try{await this._cdpSession?.send("Page.stopScreencast")}catch{}try{await this._cdpSession?.detach()}catch{}try{await this._videoRecorder?.stop()}catch(err){debug(`screen-recorder: error stopping ffmpeg: ${err}`)}return this._page&&this._pageCrashHandler&&this._page.removeListener("crash",this._pageCrashHandler),this._cdpSession=null,this._videoRecorder=null,this._filePath="",this._page=null,this._pageCrashHandler=null,debug(`screen-recorder: stopped, saved to ${filePath}`),{filePath}}};var BrowserToolSessionContext=class _BrowserToolSessionContext{static STATIC_RESOURCE_TYPES=new Set(["image","stylesheet","font","media","script","texttrack","manifest"]);static STATIC_ASSET_EXT=/\.(js|mjs|cjs|map|css|woff2?|ttf|otf|eot|png|jpe?g|gif|webp|svg|ico|mp4|webm|mp3|wav|pdf)(\?|$)/i;_sessionId;options;otelController;consoleMessages=[];httpRequests=[];initialized=!1;closed=!1;traceId;traceState;_numOfInFlightRequests=0;_lastNetworkActivityTimestamp=0;_refMap={};_consoleSeq=0;_httpSeq=0;browserContext;_page;_screenRecorder=new ScreenRecorder;_ensurePagePromise=null;_scenarioDepth=0;get page(){return this._page}constructor(sessionId,browserContext,page,options){this._sessionId=sessionId,this.browserContext=browserContext,this._page=page,this.options=options,this.otelController=new OTELController(this.browserContext)}async ensureSessionPageOpen(){if(this.closed)throw new Error("Session context is already closed");if(this._page.isClosed()){if(this._ensurePagePromise){await this._ensurePagePromise;return}this._ensurePagePromise=(async()=>{debug(`Session ${this._sessionId}: page closed; opening new tab as session page.`),this._screenRecorder.isRecording()&&await this._screenRecorder.stop(),this._refMap={},this._numOfInFlightRequests=0,this._lastNetworkActivityTimestamp=0,this._page=await this.browserContext.newPage(),this._attachPageListeners(this._page)})();try{await this._ensurePagePromise}finally{this._ensurePagePromise=null}}}_attachPageListeners(page){let me=this;page.on("console",msg=>{me.consoleMessages.push(me._toConsoleMessage(msg,++me._consoleSeq)),me.consoleMessages.length>BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE&&me.consoleMessages.splice(0,me.consoleMessages.length-BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE)}),page.on("pageerror",err=>{me.consoleMessages.push(me._errorToConsoleMessage(err,++me._consoleSeq)),me.consoleMessages.length>BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE&&me.consoleMessages.splice(0,me.consoleMessages.length-BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE)}),page.on("request",async req=>{me.otelController.isOTELRequest(req)||(me._numOfInFlightRequests++,me._lastNetworkActivityTimestamp=Date.now())}),page.on("requestfinished",async req=>{me.otelController.isOTELRequest(req)||(me._numOfInFlightRequests--,me._lastNetworkActivityTimestamp=Date.now(),me.httpRequests.push(await me._toHttpRequest(req,++me._httpSeq)),me.httpRequests.length>BROWSER_HTTP_REQUESTS_BUFFER_SIZE&&me.httpRequests.splice(0,me.httpRequests.length-BROWSER_HTTP_REQUESTS_BUFFER_SIZE))}),page.on("requestfailed",async req=>{me.otelController.isOTELRequest(req)||(me._numOfInFlightRequests--,me._lastNetworkActivityTimestamp=Date.now(),me.httpRequests.push(await me._toHttpRequest(req,++me._httpSeq)),me.httpRequests.length>BROWSER_HTTP_REQUESTS_BUFFER_SIZE&&me.httpRequests.splice(0,me.httpRequests.length-BROWSER_HTTP_REQUESTS_BUFFER_SIZE))})}async init(){if(this.closed)throw new Error("Session context is already closed");if(this.initialized)throw new Error("Session context is already initialized");this._attachPageListeners(this._page),this.options.otelEnable&&(this.traceId=newTraceId(),await this.otelController.init({traceId:this.traceId,traceState:this.traceState})),this.initialized=!0}_toConsoleMessageLevelName(type){switch(type){case"assert":case"error":return"error";case"warning":return"warning";case"count":case"dir":case"dirxml":case"info":case"log":case"table":case"time":case"timeEnd":return"info";case"clear":case"debug":case"endGroup":case"profile":case"profileEnd":case"startGroup":case"startGroupCollapsed":case"trace":return"debug";default:return"info"}}_toConsoleMessage(message,sequenceNumber){let timestamp=Date.now(),levelName=this._toConsoleMessageLevelName(message.type()),levelCode=ConsoleMessageLevel[levelName].code;return{type:message.type(),text:message.text(),level:{name:levelName,code:levelCode},location:{url:message.location().url,lineNumber:message.location().lineNumber,columnNumber:message.location().columnNumber},timestamp,sequenceNumber}}_errorToConsoleMessage(error2,sequenceNumber){let timestamp=Date.now();return error2 instanceof Error?{type:"error",text:error2.message,level:{name:"error",code:3},timestamp,sequenceNumber}:{type:"error",text:String(error2),level:{name:"error",code:3},timestamp,sequenceNumber}}_isStaticResourceUrl(url){try{let pathname=new URL(url).pathname;return _BrowserToolSessionContext.STATIC_ASSET_EXT.test(pathname)}catch{return!1}}_isBodyLikelyPresent(status,method){return!(method==="HEAD"||method==="OPTIONS"||status===204||status===304||status>=300&&status<400)}async _safeReadResponseBody(res){try{let method=res.request().method(),status=res.status();return this._isBodyLikelyPresent(status,method)?(await res.body()).toString("utf-8"):void 0}catch{return}}async _toHttpRequest(req,sequenceNumber){let res=await req.response(),resourceType=req.resourceType(),skipResponseBody=_BrowserToolSessionContext.STATIC_RESOURCE_TYPES.has(resourceType)||this._isStaticResourceUrl(req.url());return{url:req.url(),method:req.method(),headers:req.headers(),body:req.postData()||void 0,resourceType,failure:req.failure()?.errorText,duration:req.timing().responseEnd,response:res?{status:res.status(),statusText:res.statusText(),headers:res.headers(),body:skipResponseBody?void 0:await this._safeReadResponseBody(res)}:void 0,ok:res?res.ok():!1,timestamp:Math.floor(req.timing().startTime),sequenceNumber}}numOfInFlightRequests(){return this._numOfInFlightRequests}lastNetworkActivityTimestamp(){return this._lastNetworkActivityTimestamp}sessionId(){return this._sessionId}async getTraceId(){return this.traceId}async getTraceState(){return this.traceState}async getTraceContextFromBrowser(){if(!this.options.otelEnable)return{traceId:this.traceId,traceState:this.traceState};let traceId=await this.otelController.getTraceId(this.page),traceState=await this.otelController.getTraceState(this.page);return{traceId,traceState}}async setTraceId(traceId){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");let normalized=normalizeTraceId(traceId);this.traceId=normalized,await this.otelController.setTraceId(this.page,normalized)}async setTraceState(traceState){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");let trimmed=traceState.trim();trimmed&&assertValidTracestateForSet(trimmed),this.traceState=trimmed||void 0,await this.otelController.setTraceState(this.page,traceState)}async clearTraceState(){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");this.traceState=void 0,await this.otelController.setTraceState(this.page,"")}async resetTraceId(){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");let traceId=newTraceId();this.traceId=traceId,await this.otelController.setTraceId(this.page,traceId)}async clearTraceId(){if(!this.options.otelEnable)throw new Error("OTEL is not enabled");this.traceId=void 0,await this.otelController.setTraceId(this.page,"")}async useTraceContextIfOTELEnabled(args){this.options.otelEnable&&(typeof args.traceId=="string"&&(args.traceId.trim()?normalizeTraceId(args.traceId)!==this.traceId&&await this.setTraceId(args.traceId):await this.clearTraceId()),typeof args.traceState=="string"&&(args.traceState.trim()?args.traceState.trim()!==this.traceState&&await this.setTraceState(args.traceState):await this.clearTraceState()))}getConsoleMessages(){return this.consoleMessages}getHttpRequests(){return this.httpRequests}getRefMap(){return this._refMap}setRefMap(refs){this._refMap=refs}isRecording(){return this._screenRecorder.isRecording()}async startRecording(options){await this._screenRecorder.start(this.browserContext,this._page,options)}async stopRecording(){return this._screenRecorder.stop()}scenarioDepth(){return this._scenarioDepth}incrementScenarioDepth(){this._scenarioDepth++}decrementScenarioDepth(){this._scenarioDepth--}executionContext(){return{page:this.page}}async close(){if(this.closed)return!1;this._screenRecorder.isRecording()&&await this._screenRecorder.stop(),debug(`Closing OTEL controller of the session with id ${this._sessionId} ...`),await this.otelController.close();try{this.options.dontCloseBrowserContext?(debug(`Closing session page only (CDP attach) for session ${this._sessionId} ...`),await this.page.close({runBeforeUnload:!1}).catch(()=>{})):(debug(`Closing browser context of the session with id ${this._sessionId} ...`),await closeBrowserContext(this.browserContext))}catch(err){debug(`Error occurred while closing browser context of the session with id ${this._sessionId} ...`,err)}return this.consoleMessages.length=0,this.httpRequests.length=0,this._refMap={},this.closed=!0,!0}};init_logger();import{randomUUID}from"node:crypto";import{request as httpsRequest}from"node:https";import{request as httpRequest}from"node:http";var SEND_TIMEOUT_MS=3e3,EVENTS_PATH="/v1/events";async function sendToolCallToCollector(metadata,toolName,toolInput,result){if(!metadata?.sessionId||!metadata.projectName||!metadata.verificationId||!metadata.traceId)return;let collectorUrl=metadata.collectorUrl??COLLECTOR_URL,collectorApiKey=metadata.collectorApiKey??COLLECTOR_API_KEY;if(!collectorUrl||!collectorApiKey)return;let cleanToolInput=toolInput&&typeof toolInput=="object"&&!Array.isArray(toolInput)?{...toolInput,_metadata:void 0}:toolInput,event={id:randomUUID(),type:"tool_call",timestamp:Date.now(),session_id:metadata.sessionId,project_name:metadata.projectName,verification_id:metadata.verificationId,trace_id:metadata.traceId,tool_name:toolName,tool_input:cleanToolInput,tool_response:result.tool_response,duration:result.duration,...result.error?{error:result.error}:{},source:"browser-devtools-mcp"},body=JSON.stringify([event]);return new Promise(resolve=>{try{let url=new URL(EVENTS_PATH,collectorUrl),isHttps=url.protocol==="https:",doRequest=isHttps?httpsRequest:httpRequest,timeout=setTimeout(()=>{debug("collector: send timeout"),resolve()},SEND_TIMEOUT_MS),req=doRequest({hostname:url.hostname,port:url.port||(isHttps?443:80),path:url.pathname,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(body),"X-API-Key":collectorApiKey},timeout:SEND_TIMEOUT_MS},res=>{res.on("data",()=>{}),res.on("end",()=>{clearTimeout(timeout),resolve()}),res.on("close",()=>{clearTimeout(timeout),resolve()})});req.on("error",err=>{clearTimeout(timeout),debug(`collector: send error: ${err.message}`),resolve()}),req.on("timeout",()=>{clearTimeout(timeout),req.destroy(),debug("collector: request timeout"),resolve()}),req.write(body),req.end()}catch(err){debug(`collector: sendToolCallToCollector failed: ${err}`),resolve()}})}init_logger();import{request as httpRequest2}from"node:http";import{request as httpsRequest2}from"node:https";var PRESIGN_TIMEOUT_MS=3e3,UPLOAD_TIMEOUT_MS=5e3;async function uploadArtifact(collectorUrl,apiKey,artifactType,data,contentType,sessionId){try{let presigned=await _getPresignedUrl(collectorUrl,apiKey,artifactType,contentType,sessionId);return!presigned||!await _putToS3(presigned.url,data,contentType)?void 0:{key:presigned.key}}catch(err){debug(`artifact-uploader: upload failed: ${err}`);return}}function _getPresignedUrl(collectorUrl,apiKey,artifactType,contentType,sessionId){return new Promise(resolve=>{try{let url=new URL(`/v1/artifacts/${artifactType}/upload-url`,collectorUrl);url.searchParams.set("contentType",contentType),url.searchParams.set("sessionId",sessionId);let isHttps=url.protocol==="https:",doRequest=isHttps?httpsRequest2:httpRequest2,req,timeout=setTimeout(()=>{debug("artifact-uploader: presign timeout"),req?.destroy(),resolve(void 0)},PRESIGN_TIMEOUT_MS);req=doRequest({hostname:url.hostname,port:url.port||(isHttps?443:80),path:url.pathname+url.search,method:"GET",headers:{"X-API-Key":apiKey},timeout:PRESIGN_TIMEOUT_MS},res=>{let chunks=[];res.on("data",chunk=>{chunks.push(chunk)}),res.on("end",()=>{if(clearTimeout(timeout),res.statusCode!==200){debug(`artifact-uploader: presign returned ${res.statusCode}`),resolve(void 0);return}try{let body=JSON.parse(Buffer.concat(chunks).toString("utf-8"));typeof body.url=="string"&&typeof body.key=="string"?resolve({url:body.url,key:body.key}):(debug("artifact-uploader: presign response missing url/key"),resolve(void 0))}catch{debug("artifact-uploader: presign response parse error"),resolve(void 0)}}),res.on("close",()=>{clearTimeout(timeout)})}),req.on("error",err=>{clearTimeout(timeout),debug(`artifact-uploader: presign error: ${err.message}`),resolve(void 0)}),req.on("timeout",()=>{clearTimeout(timeout),req.destroy(),debug("artifact-uploader: presign request timeout"),resolve(void 0)}),req.end()}catch(err){debug(`artifact-uploader: presign failed: ${err}`),resolve(void 0)}})}function _putToS3(presignedUrl,data,contentType){return new Promise(resolve=>{try{let url=new URL(presignedUrl),isHttps=url.protocol==="https:",doRequest=isHttps?httpsRequest2:httpRequest2,req,timeout=setTimeout(()=>{debug("artifact-uploader: S3 upload timeout"),req?.destroy(),resolve(!1)},UPLOAD_TIMEOUT_MS);req=doRequest({hostname:url.hostname,port:url.port||(isHttps?443:80),path:url.pathname+url.search,method:"PUT",headers:{"Content-Type":contentType,"Content-Length":data.length},timeout:UPLOAD_TIMEOUT_MS},res=>{res.on("data",()=>{}),res.on("end",()=>{clearTimeout(timeout),res.statusCode&&res.statusCode>=200&&res.statusCode<300?resolve(!0):(debug(`artifact-uploader: S3 upload returned ${res.statusCode}`),resolve(!1))}),res.on("close",()=>{clearTimeout(timeout)})}),req.on("error",err=>{clearTimeout(timeout),debug(`artifact-uploader: S3 upload error: ${err.message}`),resolve(!1)}),req.on("timeout",()=>{clearTimeout(timeout),req.destroy(),debug("artifact-uploader: S3 upload request timeout"),resolve(!1)}),req.write(data),req.end()}catch(err){debug(`artifact-uploader: S3 upload failed: ${err}`),resolve(!1)}})}init_logger();import fs4 from"node:fs/promises";var BrowserToolExecutor=class{async executeTool(context,tool,args){debug(`Executing tool ${tool.name()} with input: ${toJson(args)}`);let startTime=Date.now();try{args._metadata&&await context.useTraceContextIfOTELEnabled({traceId:typeof args._metadata.traceId=="string"?args._metadata.traceId:void 0,traceState:typeof args._metadata.traceState=="string"?args._metadata.traceState:void 0}),await context.ensureSessionPageOpen();let result=await tool.handle(context,args);return debug(`Executed tool ${tool.name()} and got output: ${toJson(result)}`),this._uploadArtifactsAndReport(args._metadata,tool.name(),args,result,Date.now()-startTime),result}catch(err){throw debug(`Error occurred while executing ${tool.name()}: ${err}`),sendToolCallToCollector(args._metadata,tool.name(),args,{duration:Date.now()-startTime,error:err instanceof Error?err.message:String(err)}),err}}async _uploadArtifactsAndReport(metadata,toolName,toolInput,result,duration){let collectorUrl=metadata?.collectorUrl??COLLECTOR_URL,collectorApiKey=metadata?.collectorApiKey??COLLECTOR_API_KEY,sessionId=metadata?.sessionId??"",canUpload=!!(collectorUrl&&collectorApiKey&&sessionId),uploadedArtifacts=[];if(canUpload){if(result._artifacts&&result._artifacts.length>0){let uploads=result._artifacts.map(async artifact=>{try{let data=await fs4.readFile(artifact.filePath),uploaded=await uploadArtifact(collectorUrl,collectorApiKey,artifact.type,data,artifact.mimeType,sessionId);uploaded&&uploadedArtifacts.push({type:artifact.type,key:uploaded.key})}catch(err){debug(`artifact-upload: failed to read/upload ${artifact.filePath}: ${err}`)}});await Promise.all(uploads)}let imageHolder=result;if(imageHolder.image?.data&&Buffer.isBuffer(imageHolder.image.data)&&uploadedArtifacts.length===0){let uploaded=await uploadArtifact(collectorUrl,collectorApiKey,"IMAGE",imageHolder.image.data,imageHolder.image.mimeType,sessionId);uploaded&&uploadedArtifacts.push({type:"IMAGE",key:uploaded.key})}}let collectorResponse={...result,_artifacts:void 0,image:void 0};uploadedArtifacts.length>0&&(collectorResponse.artifacts=uploadedArtifacts),sendToolCallToCollector(metadata,toolName,toolInput,{duration,tool_response:collectorResponse})}};var INTERACTIVE_ROLES=new Set(["button","link","textbox","checkbox","radio","combobox","listbox","menuitem","menuitemcheckbox","menuitemradio","option","searchbox","slider","spinbutton","switch","tab","treeitem"]),CONTENT_ROLES=new Set(["heading","cell","gridcell","columnheader","rowheader","listitem","article","region","main","navigation"]),STRUCTURAL_ROLES=new Set(["generic","group","list","table","row","rowgroup","grid","treegrid","menu","menubar","toolbar","tablist","tree","directory","document","application","presentation","none"]),refCounter=0;function nextRef(){return`e${++refCounter}`}function buildSelectorDescriptor(role,name){if(name!==void 0&&name!==""){let escaped=JSON.stringify(name);return`getByRole('${role}', { name: ${escaped}, exact: true })`}return`getByRole('${role}')`}function createRoleNameTracker(){let counts=new Map,refsByKey=new Map;return{getKey(role,name){return`${role}:${name??""}`},getNextIndex(role,name){let key=this.getKey(role,name),current=counts.get(key)??0;return counts.set(key,current+1),current},trackRef(role,name,ref){let key=this.getKey(role,name),refs=refsByKey.get(key)??[];refs.push(ref),refsByKey.set(key,refs)},getDuplicateKeys(){let duplicates=new Set;for(let[key,refs]of refsByKey)refs.length>1&&duplicates.add(key);return duplicates}}}function removeNthFromNonDuplicates(refs,tracker){let duplicateKeys=tracker.getDuplicateKeys();for(let entry of Object.values(refs)){let key=tracker.getKey(entry.role,entry.name);duplicateKeys.has(key)||delete entry.nth}}function getIndentLevel(line){let m=line.match(/^(\s*)/);return m?Math.floor(m[1].length/2):0}function processLine(line,refs,options,tracker){let depth=getIndentLevel(line);if(options.maxDepth!==void 0&&depth>options.maxDepth)return null;let match=line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);if(!match)return options.interactiveOnly?null:line;let[,prefix,role,name,suffix]=match,roleLower=role.toLowerCase();if(role.startsWith("/"))return line;let isInteractive=INTERACTIVE_ROLES.has(roleLower),isContent=CONTENT_ROLES.has(roleLower),isStructural=STRUCTURAL_ROLES.has(roleLower);if(options.interactiveOnly&&!isInteractive||options.compact&&isStructural&&!name)return null;if(!(isInteractive||isContent&&name))return line;let ref=nextRef(),nth=tracker.getNextIndex(roleLower,name);tracker.trackRef(roleLower,name,ref);let entry={role:roleLower,name:name??void 0,selector:buildSelectorDescriptor(roleLower,name),nth};refs[ref]=entry;let enhanced=`${prefix}${role}`;return name&&(enhanced+=` "${name}"`),enhanced+=` [ref=${ref}]`,nth>0&&(enhanced+=` [nth=${nth}]`),suffix&&suffix.trim()&&(enhanced+=suffix),enhanced}function compactTree(tree){let lines=tree.split(`
11
11
  `),result=[];for(let i=0;i<lines.length;i++){let line=lines[i];if(line.includes("[ref=")){result.push(line);continue}if(line.includes(":")&&!line.endsWith(":")){result.push(line);continue}let currentIndent=getIndentLevel(line),hasRelevantChildren=!1;for(let j=i+1;j<lines.length&&!(getIndentLevel(lines[j])<=currentIndent);j++)if(lines[j].includes("[ref=")){hasRelevantChildren=!0;break}hasRelevantChildren&&result.push(line)}return result.join(`
12
12
  `)}function processAriaTreeWithRefs(ariaTree,options={}){refCounter=0;let refs={};if(!ariaTree||!ariaTree.trim())return{tree:"(empty)",refs:{}};let lines=ariaTree.split(`
13
13
  `),result=[],tracker=createRoleNameTracker();if(options.interactiveOnly){for(let line of lines){let m=line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);if(!m)continue;let[,,role,name,suffix]=m,roleLower=role.toLowerCase();if(!INTERACTIVE_ROLES.has(roleLower))continue;let ref=nextRef(),nth=tracker.getNextIndex(roleLower,name);tracker.trackRef(roleLower,name,ref),refs[ref]={role:roleLower,name:name??void 0,selector:buildSelectorDescriptor(roleLower,name),nth};let enhanced=`- ${role}`;name&&(enhanced+=` "${name}"`),enhanced+=` [ref=${ref}]`,nth>0&&(enhanced+=` [nth=${nth}]`),suffix&&suffix.includes("[")&&(enhanced+=suffix),result.push(enhanced)}return removeNthFromNonDuplicates(refs,tracker),{tree:result.length?result.join(`
@@ -1,4 +1,4 @@
1
- import{augmentToolInputSchema}from"./core-YPBKINC4.js";import{TOOL_INPUT_METADATA_ENABLE,denormalizeToolName}from"./core-S5JHUB3Z.js";import*as fs2 from"node:fs";import{fileURLToPath}from"node:url";import{PostHog}from"posthog-node";import*as crypto from"node:crypto";import*as fs from"node:fs";import*as os from"node:os";import*as path from"node:path";var CONFIG_DIR=path.join(os.homedir(),".browser-devtools-mcp"),CONFIG_FILE=path.join(CONFIG_DIR,"config.json");function readConfig(){let existing={};if(fs.existsSync(CONFIG_FILE))try{let raw=fs.readFileSync(CONFIG_FILE,"utf-8");existing=JSON.parse(raw)}catch{}let dirty=!1;return existing.anonymousId||(existing.anonymousId=crypto.randomUUID(),dirty=!0),existing.telemetryEnabled===void 0&&(existing.telemetryEnabled=!0,dirty=!0),existing.telemetryNoticeShown===void 0&&(existing.telemetryNoticeShown=!1,dirty=!0),dirty&&_writeConfig(existing),existing}function updateConfig(updates){let current=readConfig();_writeConfig({...current,...updates})}function _writeConfig(config){try{fs.existsSync(CONFIG_DIR)||fs.mkdirSync(CONFIG_DIR,{recursive:!0}),fs.writeFileSync(CONFIG_FILE,JSON.stringify(config,null,2),"utf-8")}catch{}}var POSTHOG_API_KEY=process.env.POSTHOG_API_KEY??"phc_ekFEnQ9ipk0F1BbO0KCkaD8OaYPa4bIqqUoxsCfeFsy",POSTHOG_HOST="https://us.posthog.com",_initialized=!1,_client=null,_anonymousId="",_enabled=!1,_source="mcp-unknown",_mcpTransport,_browserDevtoolsVersion=(()=>{let candidates=[new URL("../package.json",import.meta.url),new URL("../../package.json",import.meta.url)];for(let candidate of candidates)try{let raw=fs2.readFileSync(fileURLToPath(candidate),"utf-8"),ver=JSON.parse(raw).version;if(ver)return ver}catch{}return"unknown"})();function _detectMCPClientSource(clientInfo){if(process.env.CURSOR_TRACE_ID)return"mcp-cursor";if(process.env.CLAUDE_DESKTOP)return"mcp-claude";if(clientInfo?.name){let name=clientInfo.name.toLowerCase();if(name.includes("cursor"))return"mcp-cursor";if(name.includes("claude")||name.includes("local-agent-mode"))return"mcp-claude";if(name.includes("codex")||name.includes("openai"))return"mcp-codex"}return"mcp-unknown"}function _detectCLISource(){return"cli"}function _buildBaseProperties(){return{source:_source,browser_devtools_version:_browserDevtoolsVersion,node_version:process.version,os_platform:process.platform,os_arch:process.arch,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,timestamp:new Date().toUTCString()}}function init(opts={}){if(!_initialized){_initialized=!0;try{let config=readConfig();_anonymousId=config.anonymousId;let telemetryEnabled=process.env.TELEMETRY_ENABLE!=="false";if(_enabled=config.telemetryEnabled&&!opts.disabled&&telemetryEnabled,opts.source==="cli")_source=_detectCLISource();else if(opts.source==="mcp"){let detectedSource=_detectMCPClientSource(opts.clientInfo);_source=detectedSource==="cli"?"mcp-unknown":detectedSource}else _source=_detectMCPClientSource(opts.clientInfo);if(!config.telemetryNoticeShown&&telemetryEnabled&&(process.stderr.write(`
1
+ import{augmentToolInputSchema}from"./core-72EHCUIW.js";import{TOOL_INPUT_METADATA_ENABLE,denormalizeToolName}from"./core-S5JHUB3Z.js";import*as fs2 from"node:fs";import{fileURLToPath}from"node:url";import{PostHog}from"posthog-node";import*as crypto from"node:crypto";import*as fs from"node:fs";import*as os from"node:os";import*as path from"node:path";var CONFIG_DIR=path.join(os.homedir(),".browser-devtools-mcp"),CONFIG_FILE=path.join(CONFIG_DIR,"config.json");function readConfig(){let existing={};if(fs.existsSync(CONFIG_FILE))try{let raw=fs.readFileSync(CONFIG_FILE,"utf-8");existing=JSON.parse(raw)}catch{}let dirty=!1;return existing.anonymousId||(existing.anonymousId=crypto.randomUUID(),dirty=!0),existing.telemetryEnabled===void 0&&(existing.telemetryEnabled=!0,dirty=!0),existing.telemetryNoticeShown===void 0&&(existing.telemetryNoticeShown=!1,dirty=!0),dirty&&_writeConfig(existing),existing}function updateConfig(updates){let current=readConfig();_writeConfig({...current,...updates})}function _writeConfig(config){try{fs.existsSync(CONFIG_DIR)||fs.mkdirSync(CONFIG_DIR,{recursive:!0}),fs.writeFileSync(CONFIG_FILE,JSON.stringify(config,null,2),"utf-8")}catch{}}var POSTHOG_API_KEY=process.env.POSTHOG_API_KEY??"phc_ekFEnQ9ipk0F1BbO0KCkaD8OaYPa4bIqqUoxsCfeFsy",POSTHOG_HOST="https://us.posthog.com",_initialized=!1,_client=null,_anonymousId="",_enabled=!1,_source="mcp-unknown",_mcpTransport,_browserDevtoolsVersion=(()=>{let candidates=[new URL("../package.json",import.meta.url),new URL("../../package.json",import.meta.url)];for(let candidate of candidates)try{let raw=fs2.readFileSync(fileURLToPath(candidate),"utf-8"),ver=JSON.parse(raw).version;if(ver)return ver}catch{}return"unknown"})();function _detectMCPClientSource(clientInfo){if(process.env.CURSOR_TRACE_ID)return"mcp-cursor";if(process.env.CLAUDE_DESKTOP)return"mcp-claude";if(clientInfo?.name){let name=clientInfo.name.toLowerCase();if(name.includes("cursor"))return"mcp-cursor";if(name.includes("claude")||name.includes("local-agent-mode"))return"mcp-claude";if(name.includes("codex")||name.includes("openai"))return"mcp-codex"}return"mcp-unknown"}function _detectCLISource(){return"cli"}function _buildBaseProperties(){return{source:_source,browser_devtools_version:_browserDevtoolsVersion,node_version:process.version,os_platform:process.platform,os_arch:process.arch,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,timestamp:new Date().toUTCString()}}function init(opts={}){if(!_initialized){_initialized=!0;try{let config=readConfig();_anonymousId=config.anonymousId;let telemetryEnabled=process.env.TELEMETRY_ENABLE!=="false";if(_enabled=config.telemetryEnabled&&!opts.disabled&&telemetryEnabled,opts.source==="cli")_source=_detectCLISource();else if(opts.source==="mcp"){let detectedSource=_detectMCPClientSource(opts.clientInfo);_source=detectedSource==="cli"?"mcp-unknown":detectedSource}else _source=_detectMCPClientSource(opts.clientInfo);if(!config.telemetryNoticeShown&&telemetryEnabled&&(process.stderr.write(`
2
2
  Telemetry is enabled by default to help improve this tool.
3
3
  Run with --no-telemetry for CLI and set TELEMETRY_ENABLE=false for MCP Servers to disable.
4
4
 
@@ -1 +1 @@
1
- import{Execute,init,shutdown,trackToolCalled}from"./core-MHH6YZLG.js";import{ScenarioRun,ToolRegistry,augmentToolInputSchema,isToolEnabled,platformInfo}from"./core-YPBKINC4.js";import{AVAILABLE_TOOL_DOMAINS,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,debug,enable,error,info,init_logger,isDebugEnabled}from"./core-S5JHUB3Z.js";import{createRequire}from"node:module";init_logger();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){session.closed=!0;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)}async function _createSession(ctx,sessionId){let now=Date.now(),session={id:sessionId,context:await platformInfo.toolsInfo.createToolSessionContext(()=>sessionId),toolExecutor:platformInfo.toolsInfo.createToolExecutor(),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=await _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)session.closed||(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){init({source:"cli"});let allowedDomains=AVAILABLE_TOOL_DOMAINS,allTools=platformInfo.toolsInfo.tools.filter(tool=>isToolEnabled(tool)),toolsToExpose=allowedDomains===void 0?allTools:allTools.filter(tool=>{let domain=tool.name().split("_")[0]?.toLowerCase()??"";return allowedDomains.has(domain)}),toolRegistry=new ToolRegistry;for(let tool of toolsToExpose)toolRegistry.addTool(tool);let executeTool=new Execute(toolRegistry,platformInfo.toolsInfo.executeImportantDescription,platformInfo.toolsInfo.executeDescription);toolRegistry.addTool(executeTool);let scenarioRunTool=new ScenarioRun(toolRegistry),toolMap=Object.fromEntries(toolsToExpose.map(tool=>[tool.name(),tool]));toolMap[executeTool.name()]=executeTool,toolMap[scenarioRunTool.name()]=scenarioRunTool,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),await shutdown(),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),await shutdown(),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(augmentToolInputSchema(tool.inputSchema())).strict().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)}let toolStartTime=Date.now();try{let toolOutput=await session.toolExecutor.executeTool(session.context,tool,toolInput);trackToolCalled({toolName:tool.name(),durationMs:Date.now()-toolStartTime,success:!0,sessionId:session.id});let toolCallResponse={toolOutput};return ctx.json(toolCallResponse,200)}catch(err){trackToolCalled({toolName:toolCallRequest.toolName,durationMs:Date.now()-toolStartTime,success:!1,sessionId:session.id,error:err});let toolCallResponse={toolError:{code:err.code,message:err.message}};return ctx.json(toolCallResponse,500)}}catch(err){error("Error occurred while handling tool call request",err);let message=err instanceof Error?err.message:typeof err=="string"?err:"Internal Server Error";return ctx.json(_buildErrorResponse(500,message),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};
1
+ import{Execute,init,shutdown,trackToolCalled}from"./core-HGUZN6LG.js";import{ScenarioRun,ToolRegistry,augmentToolInputSchema,isToolEnabled,platformInfo}from"./core-72EHCUIW.js";import{AVAILABLE_TOOL_DOMAINS,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,debug,enable,error,info,init_logger,isDebugEnabled}from"./core-S5JHUB3Z.js";import{createRequire}from"node:module";init_logger();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){session.closed=!0;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)}async function _createSession(ctx,sessionId){let now=Date.now(),session={id:sessionId,context:await platformInfo.toolsInfo.createToolSessionContext(()=>sessionId),toolExecutor:platformInfo.toolsInfo.createToolExecutor(),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=await _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)session.closed||(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){init({source:"cli"});let allowedDomains=AVAILABLE_TOOL_DOMAINS,allTools=platformInfo.toolsInfo.tools.filter(tool=>isToolEnabled(tool)),toolsToExpose=allowedDomains===void 0?allTools:allTools.filter(tool=>{let domain=tool.name().split("_")[0]?.toLowerCase()??"";return allowedDomains.has(domain)}),toolRegistry=new ToolRegistry;for(let tool of toolsToExpose)toolRegistry.addTool(tool);let executeTool=new Execute(toolRegistry,platformInfo.toolsInfo.executeImportantDescription,platformInfo.toolsInfo.executeDescription);toolRegistry.addTool(executeTool);let scenarioRunTool=new ScenarioRun(toolRegistry),toolMap=Object.fromEntries(toolsToExpose.map(tool=>[tool.name(),tool]));toolMap[executeTool.name()]=executeTool,toolMap[scenarioRunTool.name()]=scenarioRunTool,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),await shutdown(),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),await shutdown(),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(augmentToolInputSchema(tool.inputSchema())).strict().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)}let toolStartTime=Date.now();try{let toolOutput=await session.toolExecutor.executeTool(session.context,tool,toolInput);trackToolCalled({toolName:tool.name(),durationMs:Date.now()-toolStartTime,success:!0,sessionId:session.id});let toolCallResponse={toolOutput};return ctx.json(toolCallResponse,200)}catch(err){trackToolCalled({toolName:toolCallRequest.toolName,durationMs:Date.now()-toolStartTime,success:!1,sessionId:session.id,error:err});let toolCallResponse={toolError:{code:err.code,message:err.message}};return ctx.json(toolCallResponse,500)}}catch(err){error("Error occurred while handling tool call request",err);let message=err instanceof Error?err.message:typeof err=="string"?err:"Internal Server Error";return ctx.json(_buildErrorResponse(500,message),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{Execute,init,shutdown,trackMCPServerStarted,trackToolCalled}from"./core-MHH6YZLG.js";import{ScenarioRun,ToolRegistry,augmentToolInputSchema,isToolEnabled,platformInfo}from"./core-YPBKINC4.js";import{AVAILABLE_TOOL_DOMAINS,LOG_FILE,PORT,SESSION_CLOSE_ON_SOCKET_CLOSE,SESSION_IDLE_CHECK_SECONDS,SESSION_IDLE_SECONDS,TOOL_OUTPUT_SCHEMA_DISABLE,applyNormalizedToolNamesInText,debug,disable,enable,error,info,initFileLogging,init_logger,isDebugEnabled,normalizeToolName}from"./core-S5JHUB3Z.js";init_logger();init_logger();import{createRequire}from"node:module";var require2=createRequire(import.meta.url),SERVER_NAME="browser-devtools-mcp",SERVER_VERSION=require2("../package.json").version;function getServerInstructions(){if(!platformInfo.serverInfo.instructions)return;let parts=[];return parts.push(platformInfo.serverInfo.instructions),parts.join(`
2
+ import{Execute,init,shutdown,trackMCPServerStarted,trackToolCalled}from"./core-HGUZN6LG.js";import{ScenarioRun,ToolRegistry,augmentToolInputSchema,isToolEnabled,platformInfo}from"./core-72EHCUIW.js";import{AVAILABLE_TOOL_DOMAINS,LOG_FILE,PORT,SESSION_CLOSE_ON_SOCKET_CLOSE,SESSION_IDLE_CHECK_SECONDS,SESSION_IDLE_SECONDS,TOOL_OUTPUT_SCHEMA_DISABLE,applyNormalizedToolNamesInText,debug,disable,enable,error,info,initFileLogging,init_logger,isDebugEnabled,normalizeToolName}from"./core-S5JHUB3Z.js";init_logger();init_logger();import{createRequire}from"node:module";var require2=createRequire(import.meta.url),SERVER_NAME="browser-devtools-mcp",SERVER_VERSION=require2("../package.json").version;function getServerInstructions(){if(!platformInfo.serverInfo.instructions)return;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 as 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};function _canonicalToolNamesForMCPDocs(){let ids=platformInfo.toolsInfo.tools.map(t=>t.name());return ids.push("execute"),ids}var 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);for(let key of Object.keys(response))key.startsWith("_")&&delete response[key];let 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 canonicalNamesForMCP=_canonicalToolNamesForMCPDocs(),mcpText=s=>{if(s)return applyNormalizedToolNamesInText(s,canonicalNamesForMCP)},server=new MCPServer({name:SERVER_NAME,version:SERVER_VERSION},{capabilities:{resources:{},tools:{}},instructions:mcpText(getServerInstructions())}),messages=[];for(let policy of getServerPolicies()??[]){let text=mcpText(policy);text&&messages.push({role:"user",content:{type:"text",text}})}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(),fallbackSessionId=crypto.randomUUID(),toolSessionContext,toolSessionContextPromise,createToolCallback=tool=>async args=>{let startTime=Date.now(),session=opts.sessionProvider?opts.sessionProvider():void 0,sessionId=session?.id||fallbackSessionId,clientName=server.server.getClientVersion()?.name;try{toolSessionContext||(session&&(toolSessionContext=session.context),toolSessionContext||(toolSessionContextPromise||(toolSessionContextPromise=platformInfo.toolsInfo.createToolSessionContext(()=>sessionId)),toolSessionContext=await toolSessionContextPromise,toolSessionContextPromise=void 0,session&&(session.context=toolSessionContext)));let response=await toolExecutor.executeTool(toolSessionContext,tool,args);return trackToolCalled({toolName:tool.name(),durationMs:Date.now()-startTime,success:!0,sessionId,clientName}),_toResponse(response)}catch(error2){return trackToolCalled({toolName:tool.name(),durationMs:Date.now()-startTime,success:!1,error:error2,sessionId,clientName}),{content:[{type:"text",text:`Error: ${error2.message}`}],isError:!0}}},includeOutputSchema=!TOOL_OUTPUT_SCHEMA_DISABLE,allowedDomains=AVAILABLE_TOOL_DOMAINS,toolRegistry=new ToolRegistry;platformInfo.toolsInfo.tools.forEach(t=>{if(!isToolEnabled(t)){debug(`Skipping tool ${t.name()} (isEnabled returned false)`);return}let domain=t.name().split("_")[0]?.toLowerCase()??"";if(allowedDomains&&!allowedDomains.has(domain)){debug(`Skipping tool ${t.name()} (domain ${domain} not in AVAILABLE_TOOL_DOMAINS)`);return}debug(`Registering tool ${t.name()} ...`);let toolOptions=includeOutputSchema?{description:mcpText(t.description()),inputSchema:augmentToolInputSchema(t.inputSchema()),outputSchema:t.outputSchema()}:{description:mcpText(t.description()),inputSchema:augmentToolInputSchema(t.inputSchema())};server.registerTool(normalizeToolName(t.name()),toolOptions,createToolCallback(t)),toolRegistry.addTool(t)});let executeTool=new Execute(toolRegistry,mcpText(platformInfo.toolsInfo.executeImportantDescription),mcpText(platformInfo.toolsInfo.executeDescription)),executeToolOptions=includeOutputSchema?{description:mcpText(executeTool.description()),inputSchema:augmentToolInputSchema(executeTool.inputSchema()),outputSchema:executeTool.outputSchema()}:{description:mcpText(executeTool.description()),inputSchema:augmentToolInputSchema(executeTool.inputSchema())};server.registerTool(normalizeToolName(executeTool.name()),executeToolOptions,createToolCallback(executeTool)),toolRegistry.addTool(executeTool);let scenarioRunTool=new ScenarioRun(toolRegistry),scenarioRunToolOptions=includeOutputSchema?{description:mcpText(scenarioRunTool.description()),inputSchema:augmentToolInputSchema(scenarioRunTool.inputSchema()),outputSchema:scenarioRunTool.outputSchema()}:{description:mcpText(scenarioRunTool.description()),inputSchema:augmentToolInputSchema(scenarioRunTool.inputSchema())};return server.registerTool(normalizeToolName(scenarioRunTool.name()),scenarioRunToolOptions,createToolCallback(scenarioRunTool)),server}async function _createAndConnectServer(transport,opts){let server=_createServer({config:opts.config,sessionProvider:()=>sessions.get(transport.sessionId),transportType:opts.transportType});return await server.connect(transport),server}function _getConfig(){return{}}function _createSession(id,ctx,transport,server,closeOnTransportClose=!0){let session={id,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,closeOnTransportClose),debug(`Created MCP server session with id ${transport.sessionId}`),session}function _createOrUpdateSession(id,ctx,transport,server,closeOnTransportClose=!0){let session=sessions.get(id);return session?(session.transport=transport,session.server=server,session.closed=!1,session.lastActiveAt=Date.now(),session):_createSession(id,ctx,transport,server,closeOnTransportClose)}async function _createTransport(ctx){let serverConfig=_getConfig(),holder={},useSessionId=ctx.req.header("mcp-use-session-id"),transport=new StreamableHTTPTransport({enableJsonResponse:!0,sessionIdGenerator:()=>useSessionId||crypto.randomUUID(),onsessioninitialized:async sessionId=>{let session=_createOrUpdateSession(sessionId,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,transportType:"streamable-http"}),transport}function _getSessionId(ctx){return ctx.req.header("mcp-session-id")}async function _getTransport(ctx){let sessionId=_getSessionId(ctx);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=_getSessionId(ctx);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,closeSession=!0){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(closeSession&&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.text()}`)}function _markSessionAsActive(ctx){let sessionId=_getSessionId(ctx);if(sessionId){let session=sessions.get(sessionId);session&&(session.lastActiveAt=Date.now())}}async function startStdioServer(){init({source:"mcp"});let transport=new StdioServerTransport;await _createAndConnectServer(transport,{config:_getConfig(),transportType:"stdio"}),trackMCPServerStarted("stdio")}var app=new Hono;async function startStreamableHTTPServer(port){init({source:"mcp"}),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},()=>{trackMCPServerStarted("streamable-http"),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().allowExcessArguments().parse(process.argv).opts()}async function _shutdown(){await shutdown()}async function main(){let options=_getOptions();for(let signal of["SIGTERM","SIGINT"])process.on(signal,async()=>{await _shutdown(),process.exit(0)});LOG_FILE&&initFileLogging(LOG_FILE),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(async err=>{enable(),error("MCP server error",err),await _shutdown(),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-devtools-mcp",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "MCP Server for Browser Dev Tools",
5
5
  "private": false,
6
6
  "type": "module",