browser-devtools-mcp 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{isToolEnabled,platformInfo}from"../core-ARWM3DVU.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-G2U4OSL6.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(`
|
|
@@ -263,7 +263,7 @@ By default, all <script> tags are removed from the output unless "removeScripts"
|
|
|
263
263
|
`}inputSchema(){return{selector:z6.string().optional().describe("Selector or ref; omit for full document."),removeScripts:z6.boolean().optional().default(!0),removeComments:z6.boolean().optional().default(!1),removeStyles:z6.boolean().optional().default(!1),removeMeta:z6.boolean().optional().default(!1),cleanHtml:z6.boolean().optional().default(!1),minify:z6.boolean().optional().default(!1),maxLength:z6.number().int().positive().optional().default(DEFAULT_MAX_HTML_LENGTH)}}outputSchema(){return{output:z6.string().describe("The requested HTML content of the page.")}}async handle(context,args){let{selector,removeScripts,removeComments,removeStyles,removeMeta,minify,cleanHtml,maxLength}=args,htmlContent;if(selector){let element=await context.page.$(selector);if(!element)throw new Error(`Element with selector "${selector}" not found`);htmlContent=await element.evaluate(el=>el.outerHTML)}else htmlContent=await context.page.content();let shouldRemoveScripts=removeScripts||cleanHtml,shouldRemoveComments=removeComments||cleanHtml,shouldRemoveStyles=removeStyles||cleanHtml,shouldRemoveMeta=removeMeta||cleanHtml;(shouldRemoveScripts||shouldRemoveComments||shouldRemoveStyles||shouldRemoveMeta||minify)&&(htmlContent=await context.page.evaluate(params=>{let html=params.html,removeScripts2=params.removeScripts,removeComments2=params.removeComments,removeStyles2=params.removeStyles,removeMeta2=params.removeMeta,minify2=params.minify,template=document.createElement("template");template.innerHTML=html;let root=template.content;if(removeScripts2&&root.querySelectorAll("script").forEach(script=>script.remove()),removeStyles2&&root.querySelectorAll("style").forEach(style=>style.remove()),removeMeta2&&root.querySelectorAll("meta").forEach(meta=>meta.remove()),removeComments2){let removeComments3=node=>{let childNodes=node.childNodes;for(let i=childNodes.length-1;i>=0;i--){let child=childNodes[i];child.nodeType===8?node.removeChild(child):child.nodeType===1&&removeComments3(child)}};removeComments3(root)}let result=template.innerHTML;return minify2&&(result=result.replace(/>\s+</g,"><").trim()),result},{html:htmlContent,removeScripts:shouldRemoveScripts,removeComments:shouldRemoveComments,removeStyles:shouldRemoveStyles,removeMeta:shouldRemoveMeta,minify}));let output=htmlContent;return output.length>maxLength&&(output=output.slice(0,maxLength)+`
|
|
264
264
|
<!-- Output truncated due to size limits -->`),{output}}};import{z as z7}from"zod";var DEFAULT_MAX_TEXT_LENGTH=5e4,GetAsText=class{name(){return"content_get-as-text"}description(){return"Gets the visible text content of the current page."}inputSchema(){return{selector:z7.string().describe("Limit text to this container; omit for full page.").optional(),maxLength:z7.number().int().positive().optional().default(DEFAULT_MAX_TEXT_LENGTH).describe("Truncate after this many characters.")}}outputSchema(){return{output:z7.string().describe("The requested text content of the page.")}}async handle(context,args){let{selector,maxLength}=args,output=await context.page.evaluate(params=>{let selector2=params.selector,root=selector2?document.querySelector(selector2):document.body;if(!root)throw new Error(`Element with selector "${selector2}" not found`);let walker=document.createTreeWalker(root,NodeFilter.SHOW_TEXT,{acceptNode:node2=>{let style=window.getComputedStyle(node2.parentElement);return style.display!=="none"&&style.visibility!=="hidden"?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_REJECT}}),text="",node;for(;node=walker.nextNode();){let trimmedText=node.textContent?.trim();trimmedText&&(text+=trimmedText+`
|
|
265
265
|
`)}return text.trim()},{selector});return output.length>maxLength&&(output=output.slice(0,maxLength)+`
|
|
266
|
-
[Output truncated due to size limits]`),{output}}};import os2 from"node:os";import path4 from"node:path";import{z as z8}from"zod";var PageFormat=(PageFormat2=>(PageFormat2.LETTER="Letter",PageFormat2.LEGAL="Legal",PageFormat2.TABLOID="Tabloid",PageFormat2.LEDGER="Ledger",PageFormat2.A0="A0",PageFormat2.A1="A1",PageFormat2.A2="A2",PageFormat2.A3="A3",PageFormat2.A4="A4",PageFormat2.A5="A5",PageFormat2.A6="A6",PageFormat2))(PageFormat||{}),DEFAULT_NAME2="page",DEFAULT_MARGIN="1cm",DEFAULT_FORMAT="A4",DEFAULT_MARGINS={top:DEFAULT_MARGIN,right:DEFAULT_MARGIN,bottom:DEFAULT_MARGIN,left:DEFAULT_MARGIN},SaveAsPdf=class{name(){return"content_save-as-pdf"}description(){return"Saves the current page as a PDF file."}inputSchema(){return{outputPath:z8.string().optional().default(os2.tmpdir()),name:z8.string().optional().default(DEFAULT_NAME2),format:z8.enum(getEnumKeyTuples(PageFormat)).transform(createEnumTransformer(PageFormat)).optional().default(DEFAULT_FORMAT).describe("Page size."),printBackground:z8.boolean().optional().default(!1).describe("Background."),margin:z8.object({top:z8.string().default(DEFAULT_MARGIN),right:z8.string().default(DEFAULT_MARGIN),bottom:z8.string().default(DEFAULT_MARGIN),left:z8.string().default(DEFAULT_MARGIN)}).optional().describe("Margin (e.g. 1cm).")}}outputSchema(){return{filePath:z8.string().describe("Full path of the saved PDF file.")}}async handle(context,args){let filename=`${args.name||DEFAULT_NAME2}-${formattedTimeForFilename()}.pdf`,filePath=path4.resolve(args.outputPath,filename),options={path:filePath,format:args.format,printBackground:args.printBackground,margin:args.margin||DEFAULT_MARGINS};return await context.page.pdf(options),{filePath}}};import os3 from"node:os";import{z as z9}from"zod";var StartRecording=class{name(){return"content_start-recording"}description(){return`
|
|
266
|
+
[Output truncated due to size limits]`),{output}}};import fs6 from"node:fs/promises";import os2 from"node:os";import path4 from"node:path";import{z as z8}from"zod";var PageFormat=(PageFormat2=>(PageFormat2.LETTER="Letter",PageFormat2.LEGAL="Legal",PageFormat2.TABLOID="Tabloid",PageFormat2.LEDGER="Ledger",PageFormat2.A0="A0",PageFormat2.A1="A1",PageFormat2.A2="A2",PageFormat2.A3="A3",PageFormat2.A4="A4",PageFormat2.A5="A5",PageFormat2.A6="A6",PageFormat2))(PageFormat||{}),DEFAULT_NAME2="page",DEFAULT_MARGIN="1cm",DEFAULT_FORMAT="A4",DEFAULT_MARGINS={top:DEFAULT_MARGIN,right:DEFAULT_MARGIN,bottom:DEFAULT_MARGIN,left:DEFAULT_MARGIN},SaveAsPdf=class{name(){return"content_save-as-pdf"}description(){return"Saves the current page as a PDF file."}inputSchema(){return{outputPath:z8.string().optional().default(os2.tmpdir()),name:z8.string().optional().default(DEFAULT_NAME2),format:z8.enum(getEnumKeyTuples(PageFormat)).transform(createEnumTransformer(PageFormat)).optional().default(DEFAULT_FORMAT).describe("Page size."),printBackground:z8.boolean().optional().default(!1).describe("Background."),margin:z8.object({top:z8.string().default(DEFAULT_MARGIN),right:z8.string().default(DEFAULT_MARGIN),bottom:z8.string().default(DEFAULT_MARGIN),left:z8.string().default(DEFAULT_MARGIN)}).optional().describe("Margin (e.g. 1cm).")}}outputSchema(){return{filePath:z8.string().describe("Full path of the saved PDF file.")}}async handle(context,args){let filename=`${args.name||DEFAULT_NAME2}-${formattedTimeForFilename()}.pdf`,filePath=path4.resolve(args.outputPath,filename),options={path:filePath,format:args.format,printBackground:args.printBackground,margin:args.margin||DEFAULT_MARGINS};return await fs6.mkdir(path4.dirname(filePath),{recursive:!0}),await context.page.pdf(options),{filePath}}};import os3 from"node:os";import{z as z9}from"zod";var StartRecording=class{name(){return"content_start-recording"}description(){return`
|
|
267
267
|
Starts video recording of the browser page.
|
|
268
268
|
Recording captures all page interactions until <content_stop-recording> is called.
|
|
269
269
|
Uses CDP screencast \u2014 works in all modes (headless, headed, persistent, CDP attach).
|
|
@@ -271,12 +271,12 @@ Only supported on Chromium-based browsers.
|
|
|
271
271
|
`.trim()}inputSchema(){return{outputDir:z9.string().optional().default(os3.tmpdir()).describe("Directory where the video file will be saved."),name:z9.string().optional().describe('Name for the video file (without extension). Defaults to "recording".')}}outputSchema(){return{message:z9.string().describe("Status message.")}}async handle(context,args){return context.isRecording()?{message:"Recording is already in progress."}:(await context.startRecording({outputDir:args.outputDir??os3.tmpdir(),name:args.name}),{message:"Recording started."})}};init_logger();import{z as z10}from"zod";var StopRecording=class{name(){return"content_stop-recording"}description(){return`
|
|
272
272
|
Stops video recording of the browser page and saves the video file.
|
|
273
273
|
Must be called after <content_start-recording>. The video is saved as a WebM file.
|
|
274
|
-
`.trim()}inputSchema(){return{}}outputSchema(){return{filePath:z10.string().optional().describe("Full path of the saved video file.")}}async handle(context,_args){if(!context.isRecording())return debug("stop-recording: isRecording() returned false"),{};let result=await context.stopRecording();if(!result)return debug("stop-recording: stopRecording() returned undefined"),{};let artifacts=[{type:"VIDEO",filePath:result.filePath,mimeType:"video/webm"}];return{filePath:result.filePath,_artifacts:artifacts}}};var REF_ROLES_INTERACTIVE=new Set(["button","link","textbox","searchbox","checkbox","radio","combobox","listbox","menuitem","menuitemcheckbox","menuitemradio","option","slider","spinbutton","switch","tab","tabpanel","treeitem","listitem","row","cell","gridcell","columnheader","rowheader","separator","dialog","alertdialog","clickable","focusable"]);function parseQuotedString(s,i){let q=s[i];if(q!=="'"&&q!=='"')return null;let j=i+1,parts=[];for(;j<s.length;){let c=s[j];if(c==="\\"){j++,j<s.length&&parts.push(s[j]),j++;continue}if(c===q)return{value:parts.join(""),end:j+1};parts.push(c),j++}return null}function parseRegexLiteral(s,i){if(s[i]!=="/")return null;let j=i+1;for(;j<s.length;){if(s[j]==="\\"){j+=2;continue}if(s[j]==="/")break;j++}if(j>=s.length)return null;let pattern=s.slice(i+1,j);j++;let flags="";for(;j<s.length&&/[gimsuy]/.test(s[j])&&!flags.includes(s[j]);)flags+=s[j],j++;try{return{value:new RegExp(pattern,flags),end:j}}catch{return null}}function parseOptionsObject(s,i){for(;i<s.length&&/\s/.test(s[i]);)i++;if(s[i]!=="{")return null;i++;let obj={};for(;i<s.length;){for(;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return null;if(s[i]==="}")return{value:obj,end:i+1};let keyMatch=s.slice(i).match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:/);if(!keyMatch)return null;let key=keyMatch[1];for(i+=keyMatch[0].length;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return null;if(s[i]==="'"||s[i]==='"'){let parsed=parseQuotedString(s,i);if(!parsed)return null;obj[key]=parsed.value,i=parsed.end}else if(s[i]==="/"){let parsed=parseRegexLiteral(s,i);if(!parsed)return null;obj[key]=parsed.value,i=parsed.end}else if(s.slice(i).startsWith("true"))obj[key]=!0,i+=4;else if(s.slice(i).startsWith("false"))obj[key]=!1,i+=5;else{let numMatch=s.slice(i).match(/^-?\d+/);if(numMatch)obj[key]=parseInt(numMatch[0],10),i+=numMatch[0].length;else return null}for(;i<s.length&&/\s/.test(s[i]);)i++;if(i<s.length&&s[i]===","){i++;continue}break}for(;i<s.length&&/\s/.test(s[i]);)i++;return s[i]!=="}"?null:{value:obj,end:i+1}}function parseGetByArgs(inner){let s=inner.trim(),i=0;for(;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return null;let first;if(s[i]==="'"||s[i]==='"'){let firstParsed=parseQuotedString(s,i);if(!firstParsed)return null;first=firstParsed.value,i=firstParsed.end}else if(s[i]==="/"){let regexParsed=parseRegexLiteral(s,i);if(!regexParsed)return null;first=regexParsed.value,i=regexParsed.end}else return null;for(;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return{first};if(s[i]!==",")return{first};i++;let optParsed=parseOptionsObject(s,i);return optParsed?{first,options:optParsed.value}:{first}}function tryParsePlaywrightLocatorExpression(page,selectorOrExpression){let match=selectorOrExpression.trim().match(/^\s*(getByRole|getByLabel|getByText|getByPlaceholder|getByAltText|getByTitle|getByTestId)\s*\(\s*([\s\S]*)\s*\)\s*$/);if(!match)return null;let method=match[1],argsInner=match[2],parsed=parseGetByArgs(argsInner);if(!parsed)return null;let{first,options}=parsed;if(method==="getByTestId")return page.getByTestId(first).first();if(method==="getByRole"){if(typeof first!="string")return null;let opts=options??{},roleOpts={};return opts.name!==void 0&&(roleOpts.name=typeof opts.name=="string"||opts.name instanceof RegExp?opts.name:String(opts.name)),opts.exact!==void 0&&(roleOpts.exact=!!opts.exact),opts.checked!==void 0&&(roleOpts.checked=!!opts.checked),opts.disabled!==void 0&&(roleOpts.disabled=!!opts.disabled),opts.expanded!==void 0&&(roleOpts.expanded=!!opts.expanded),opts.includeHidden!==void 0&&(roleOpts.includeHidden=!!opts.includeHidden),opts.level!==void 0&&(roleOpts.level=Number(opts.level)),opts.pressed!==void 0&&(roleOpts.pressed=!!opts.pressed),opts.selected!==void 0&&(roleOpts.selected=!!opts.selected),page.getByRole(first,Object.keys(roleOpts).length>0?roleOpts:void 0).first()}let exactOpts=options&&options.exact!==void 0?{exact:!!options.exact}:void 0;switch(method){case"getByLabel":return page.getByLabel(first,exactOpts).first();case"getByText":return page.getByText(first,exactOpts).first();case"getByPlaceholder":return page.getByPlaceholder(first,exactOpts).first();case"getByAltText":return page.getByAltText(first,exactOpts).first();case"getByTitle":return page.getByTitle(first,exactOpts).first();default:return null}}function parseRef(arg){let s=arg.trim();return s.startsWith("@")?s.slice(1):s.toLowerCase().startsWith("ref=")?s.slice(4):/^e\d+$/.test(s)?s:null}function getLocatorFromRef(page,refMap,ref){let data=refMap[ref];if(!data)return null;if(data.role==="clickable"||data.role==="focusable"){let locator2=page.locator(data.selector);return data.nth!==void 0?locator2.nth(data.nth):locator2.first()}let locator;return data.name!==void 0&&data.name!==""?locator=page.getByRole(data.role,{name:data.name,exact:!0}):locator=page.getByRole(data.role),data.nth!==void 0?locator.nth(data.nth):locator.first()}function assertRefRole(context,selectorOrRef,allowedRoles,actionLabel,hint){let ref=parseRef(selectorOrRef),refMap=context.getRefMap();if(!ref||!refMap[ref])return;let entry=refMap[ref];if(allowedRoles.has(entry.role))return;let namePart=entry.name?` "${entry.name}"`:"";throw new Error(`Ref ${ref} points to a ${entry.role}${namePart}, which is not valid for ${actionLabel}. ${hint}`)}function resolveSelectorOrRef(context,selectorOrRef){let ref=parseRef(selectorOrRef),refMap=context.getRefMap();if(ref&&Object.keys(refMap).length>0){let locator=getLocatorFromRef(context.page,refMap,ref);if(locator)return locator}let playwrightLocator=tryParsePlaywrightLocatorExpression(context.page,selectorOrRef);return playwrightLocator||context.page.locator(selectorOrRef).first()}import jpegjs from"jpeg-js";import{PNG}from"pngjs";var ANNOTATION_OVERLAY_ID="__browser_devtools_mcp_annotations__",ANNOTATE_BOX_TIMEOUT_MS=2e3,ScreenshotType=(ScreenshotType2=>(ScreenshotType2.PNG="png",ScreenshotType2.JPEG="jpeg",ScreenshotType2))(ScreenshotType||{}),MAX_BUFFER_SIZE=800*1024,MAX_PIXELS=1.15*1024*1024,MAX_LINEAR_SIZE=1568;function scaleImageToSize(image,size){let{data:src,width:w1,height:h1}=image,w2=Math.max(1,Math.floor(size.width)),h2=Math.max(1,Math.floor(size.height));if(w1===w2&&h1===h2)return image;if(w1<=0||h1<=0)throw new Error("Invalid input image");if(size.width<=0||size.height<=0||!isFinite(size.width)||!isFinite(size.height))throw new Error("Invalid output dimensions");let clamp=(v,lo,hi)=>v<lo?lo:v>hi?hi:v,weights=(t,o)=>{let t2=t*t,t3=t2*t;o[0]=-.5*t+1*t2-.5*t3,o[1]=1-2.5*t2+1.5*t3,o[2]=.5*t+2*t2-1.5*t3,o[3]=-.5*t2+.5*t3},srcRowStride=w1*4,dstRowStride=w2*4,xOff=new Int32Array(w2*4),xW=new Float32Array(w2*4),wx=new Float32Array(4),xScale=w1/w2;for(let x=0;x<w2;x++){let sx=(x+.5)*xScale-.5,sxi=Math.floor(sx),t=sx-sxi;weights(t,wx);let b=x*4,i0=clamp(sxi-1,0,w1-1),i1=clamp(sxi+0,0,w1-1),i2=clamp(sxi+1,0,w1-1),i3=clamp(sxi+2,0,w1-1);xOff[b+0]=i0<<2,xOff[b+1]=i1<<2,xOff[b+2]=i2<<2,xOff[b+3]=i3<<2,xW[b+0]=wx[0],xW[b+1]=wx[1],xW[b+2]=wx[2],xW[b+3]=wx[3]}let yRow=new Int32Array(h2*4),yW=new Float32Array(h2*4),wy=new Float32Array(4),yScale=h1/h2;for(let y=0;y<h2;y++){let sy=(y+.5)*yScale-.5,syi=Math.floor(sy),t=sy-syi;weights(t,wy);let b=y*4,j0=clamp(syi-1,0,h1-1),j1=clamp(syi+0,0,h1-1),j2=clamp(syi+1,0,h1-1),j3=clamp(syi+2,0,h1-1);yRow[b+0]=j0*srcRowStride,yRow[b+1]=j1*srcRowStride,yRow[b+2]=j2*srcRowStride,yRow[b+3]=j3*srcRowStride,yW[b+0]=wy[0],yW[b+1]=wy[1],yW[b+2]=wy[2],yW[b+3]=wy[3]}let dst=new Uint8Array(w2*h2*4);for(let y=0;y<h2;y++){let yb=y*4,rb0=yRow[yb+0],rb1=yRow[yb+1],rb2=yRow[yb+2],rb3=yRow[yb+3],wy0=yW[yb+0],wy1=yW[yb+1],wy2=yW[yb+2],wy3=yW[yb+3],dstBase=y*dstRowStride;for(let x=0;x<w2;x++){let xb=x*4,xo0=xOff[xb+0],xo1=xOff[xb+1],xo2=xOff[xb+2],xo3=xOff[xb+3],wx0=xW[xb+0],wx1=xW[xb+1],wx2=xW[xb+2],wx3=xW[xb+3],di=dstBase+(x<<2);for(let c=0;c<4;c++){let r0=src[rb0+xo0+c]*wx0+src[rb0+xo1+c]*wx1+src[rb0+xo2+c]*wx2+src[rb0+xo3+c]*wx3,r1=src[rb1+xo0+c]*wx0+src[rb1+xo1+c]*wx1+src[rb1+xo2+c]*wx2+src[rb1+xo3+c]*wx3,r2=src[rb2+xo0+c]*wx0+src[rb2+xo1+c]*wx1+src[rb2+xo2+c]*wx2+src[rb2+xo3+c]*wx3,r3=src[rb3+xo0+c]*wx0+src[rb3+xo1+c]*wx1+src[rb3+xo2+c]*wx2+src[rb3+xo3+c]*wx3,v=r0*wy0+r1*wy1+r2*wy2+r3*wy3;dst[di+c]=v<0?0:v>255?255:v|0}}}return{data:Buffer.from(dst.buffer),width:w2,height:h2}}function scaleImageToFitMessage(buffer,screenshotType){let image=screenshotType==="png"?PNG.sync.read(buffer):jpegjs.decode(buffer,{maxMemoryUsageInMB:512}),pixels=image.width*image.height,shrink=Math.min(MAX_LINEAR_SIZE/image.width,MAX_LINEAR_SIZE/image.height,Math.sqrt(MAX_PIXELS/pixels));shrink>1&&(shrink=1);let width=image.width*shrink|0,height=image.height*shrink|0,scaledImage=scaleImageToSize(image,{width,height}),quality=screenshotType==="png"?75:70,result=screenshotType==="png"?jpegjs.encode(scaledImage,quality).data:jpegjs.encode(scaledImage,quality).data,iterations=0,MAX_ITERATIONS=5;for(;result.length>MAX_BUFFER_SIZE&&iterations<MAX_ITERATIONS;)quality=Math.max(50,quality-10),quality<=50&&result.length>MAX_BUFFER_SIZE&&(shrink*=.85,width=Math.max(200,image.width*shrink|0),height=Math.max(200,image.height*shrink|0),scaledImage=scaleImageToSize(image,{width,height})),result=jpegjs.encode(scaledImage,quality).data,iterations++;return result}async function captureScreenshot(context,options){let fullPage=options.fullPage??!1,screenshotType=options.type??"png",annotate=options.annotate??!1,annotateContent=options.annotateContent??!1,annotateCursorInteractive=options.annotateCursorInteractive??!1,quality=options.quality??(screenshotType==="png"?void 0:100),overlayInjected=!1,annotations,scopedElement=null;if(options.selector&&(scopedElement=await resolveSelectorOrRef(context,options.selector).elementHandle({timeout:1e4}),!scopedElement))throw new Error(`Element not found: ${options.selector}`);if(annotate){let refMap=context.getRefMap(),wantContent=annotateContent,wantCursorInteractive=annotateCursorInteractive;if(Object.keys(refMap).length===0||wantCursorInteractive||wantContent){let raw=await context.page.locator("body").ariaSnapshot(),{refs}=processAriaTreeWithRefs(raw,{interactiveOnly:!wantContent}),mergedRefs={...refs};if(wantCursorInteractive){let cursorEntries=await findCursorInteractiveElements(context.page,null);if(cursorEntries.length>0){let maxNum=Object.keys(mergedRefs).reduce((m,k)=>Math.max(m,parseInt(k.replace(/^e/,""),10)||0),0);cursorEntries.forEach((entry,i)=>{mergedRefs[`e${maxNum+1+i}`]={role:entry.role,name:entry.name,selector:entry.selector,nth:entry.nth}})}}context.setRefMap(mergedRefs),refMap=mergedRefs}let entries=Object.entries(refMap),allBoxes=[];for(let[ref,data]of entries){let locator=getLocatorFromRef(context.page,refMap,ref);if(!locator)continue;let box=await locator.boundingBox({timeout:ANNOTATE_BOX_TIMEOUT_MS}).catch(()=>null);if(!box||box.width===0||box.height===0)continue;let num=parseInt(ref.replace(/^e/,""),10)||0;allBoxes.push({ref,number:num,role:data.role,name:data.name,box:{x:Math.round(box.x),y:Math.round(box.y),width:Math.round(box.width),height:Math.round(box.height)}})}allBoxes.sort((a,b)=>a.number-b.number);let boxes=allBoxes,overlayBoxesViewport=allBoxes;if(scopedElement){let elementBox=await scopedElement.boundingBox();if(elementBox&&elementBox.width>0&&elementBox.height>0){let overlapping=allBoxes.filter(a=>{let b=a.box;return b.x<elementBox.x+elementBox.width&&b.x+b.width>elementBox.x&&b.y<elementBox.y+elementBox.height&&b.y+b.height>elementBox.y});overlayBoxesViewport=overlapping,boxes=overlapping.map(a=>{let b=a.box,relX=Math.max(0,b.x-elementBox.x),relY=Math.max(0,b.y-elementBox.y),relRight=Math.min(b.x+b.width-elementBox.x,elementBox.width),relBottom=Math.min(b.y+b.height-elementBox.y,elementBox.height);return{...a,box:{x:Math.round(relX),y:Math.round(relY),width:Math.round(Math.max(0,relRight-relX)),height:Math.round(Math.max(0,relBottom-relY))}}})}}if(boxes.length>0){let overlayPayload={data:overlayBoxesViewport.map(a=>({number:a.number,x:a.box.x,y:a.box.y,width:a.box.width,height:a.box.height})),id:ANNOTATION_OVERLAY_ID};if(await context.page.evaluate(({data,id})=>{let sx=window.scrollX||0,sy=window.scrollY||0,c=document.createElement("div");c.id=id,c.style.cssText="position:absolute;top:0;left:0;width:0;height:0;pointer-events:none;z-index:2147483647;";for(let it of data){let dx=it.x+sx,dy=it.y+sy,b=document.createElement("div");b.style.cssText=`position:absolute;left:${dx}px;top:${dy}px;width:${it.width}px;height:${it.height}px;border:2px solid rgba(255,0,0,0.8);box-sizing:border-box;pointer-events:none;`;let l=document.createElement("div");l.textContent=String(it.number);let labelTop=dy<14?"2px":"-14px";l.style.cssText=`position:absolute;top:${labelTop};left:-2px;background:rgba(255,0,0,0.9);color:#fff;font:bold 11px/14px monospace;padding:0 4px;border-radius:2px;white-space:nowrap;`,b.appendChild(l),c.appendChild(b)}document.documentElement.appendChild(c)},overlayPayload),overlayInjected=!0,fullPage&&!scopedElement&&boxes.length>0){let scroll=await context.page.evaluate(()=>({x:window.scrollX||0,y:window.scrollY||0}));annotations=boxes.map(a=>({...a,box:{x:a.box.x+scroll.x,y:a.box.y+scroll.y,width:a.box.width,height:a.box.height}}))}else annotations=boxes}}try{let screenshotOptions={type:screenshotType,fullPage,quality};scopedElement&&(screenshotOptions.element=scopedElement);let rawBuffer=await context.page.screenshot(screenshotOptions),scaledBuffer=scaleImageToFitMessage(rawBuffer,screenshotType);return{rawBuffer,image:{data:scaledBuffer,mimeType:`image/${screenshotType}`},annotations,screenshotType}}finally{overlayInjected&&await context.page.evaluate(id=>{let el=document.getElementById(id);el&&el.remove()},ANNOTATION_OVERLAY_ID).catch(()=>{})}}import fs6 from"node:fs/promises";import os4 from"node:os";import path5 from"node:path";import{z as z11}from"zod";var DEFAULT_SCREENSHOT_NAME="screenshot",DEFAULT_SCREENSHOT_TYPE="png",DEFAULT_SCREENSHOT_QUALITY=100,TakeScreenshot=class{name(){return"content_take-screenshot"}description(){return`
|
|
274
|
+
`.trim()}inputSchema(){return{}}outputSchema(){return{filePath:z10.string().optional().describe("Full path of the saved video file.")}}async handle(context,_args){if(!context.isRecording())return debug("stop-recording: isRecording() returned false"),{};let result=await context.stopRecording();if(!result)return debug("stop-recording: stopRecording() returned undefined"),{};let artifacts=[{type:"VIDEO",filePath:result.filePath,mimeType:"video/webm"}];return{filePath:result.filePath,_artifacts:artifacts}}};var REF_ROLES_INTERACTIVE=new Set(["button","link","textbox","searchbox","checkbox","radio","combobox","listbox","menuitem","menuitemcheckbox","menuitemradio","option","slider","spinbutton","switch","tab","tabpanel","treeitem","listitem","row","cell","gridcell","columnheader","rowheader","separator","dialog","alertdialog","clickable","focusable"]);function parseQuotedString(s,i){let q=s[i];if(q!=="'"&&q!=='"')return null;let j=i+1,parts=[];for(;j<s.length;){let c=s[j];if(c==="\\"){j++,j<s.length&&parts.push(s[j]),j++;continue}if(c===q)return{value:parts.join(""),end:j+1};parts.push(c),j++}return null}function parseRegexLiteral(s,i){if(s[i]!=="/")return null;let j=i+1;for(;j<s.length;){if(s[j]==="\\"){j+=2;continue}if(s[j]==="/")break;j++}if(j>=s.length)return null;let pattern=s.slice(i+1,j);j++;let flags="";for(;j<s.length&&/[gimsuy]/.test(s[j])&&!flags.includes(s[j]);)flags+=s[j],j++;try{return{value:new RegExp(pattern,flags),end:j}}catch{return null}}function parseOptionsObject(s,i){for(;i<s.length&&/\s/.test(s[i]);)i++;if(s[i]!=="{")return null;i++;let obj={};for(;i<s.length;){for(;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return null;if(s[i]==="}")return{value:obj,end:i+1};let keyMatch=s.slice(i).match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:/);if(!keyMatch)return null;let key=keyMatch[1];for(i+=keyMatch[0].length;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return null;if(s[i]==="'"||s[i]==='"'){let parsed=parseQuotedString(s,i);if(!parsed)return null;obj[key]=parsed.value,i=parsed.end}else if(s[i]==="/"){let parsed=parseRegexLiteral(s,i);if(!parsed)return null;obj[key]=parsed.value,i=parsed.end}else if(s.slice(i).startsWith("true"))obj[key]=!0,i+=4;else if(s.slice(i).startsWith("false"))obj[key]=!1,i+=5;else{let numMatch=s.slice(i).match(/^-?\d+/);if(numMatch)obj[key]=parseInt(numMatch[0],10),i+=numMatch[0].length;else return null}for(;i<s.length&&/\s/.test(s[i]);)i++;if(i<s.length&&s[i]===","){i++;continue}break}for(;i<s.length&&/\s/.test(s[i]);)i++;return s[i]!=="}"?null:{value:obj,end:i+1}}function parseGetByArgs(inner){let s=inner.trim(),i=0;for(;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return null;let first;if(s[i]==="'"||s[i]==='"'){let firstParsed=parseQuotedString(s,i);if(!firstParsed)return null;first=firstParsed.value,i=firstParsed.end}else if(s[i]==="/"){let regexParsed=parseRegexLiteral(s,i);if(!regexParsed)return null;first=regexParsed.value,i=regexParsed.end}else return null;for(;i<s.length&&/\s/.test(s[i]);)i++;if(i>=s.length)return{first};if(s[i]!==",")return{first};i++;let optParsed=parseOptionsObject(s,i);return optParsed?{first,options:optParsed.value}:{first}}function tryParsePlaywrightLocatorExpression(page,selectorOrExpression){let match=selectorOrExpression.trim().match(/^\s*(getByRole|getByLabel|getByText|getByPlaceholder|getByAltText|getByTitle|getByTestId)\s*\(\s*([\s\S]*)\s*\)\s*$/);if(!match)return null;let method=match[1],argsInner=match[2],parsed=parseGetByArgs(argsInner);if(!parsed)return null;let{first,options}=parsed;if(method==="getByTestId")return page.getByTestId(first).first();if(method==="getByRole"){if(typeof first!="string")return null;let opts=options??{},roleOpts={};return opts.name!==void 0&&(roleOpts.name=typeof opts.name=="string"||opts.name instanceof RegExp?opts.name:String(opts.name)),opts.exact!==void 0&&(roleOpts.exact=!!opts.exact),opts.checked!==void 0&&(roleOpts.checked=!!opts.checked),opts.disabled!==void 0&&(roleOpts.disabled=!!opts.disabled),opts.expanded!==void 0&&(roleOpts.expanded=!!opts.expanded),opts.includeHidden!==void 0&&(roleOpts.includeHidden=!!opts.includeHidden),opts.level!==void 0&&(roleOpts.level=Number(opts.level)),opts.pressed!==void 0&&(roleOpts.pressed=!!opts.pressed),opts.selected!==void 0&&(roleOpts.selected=!!opts.selected),page.getByRole(first,Object.keys(roleOpts).length>0?roleOpts:void 0).first()}let exactOpts=options&&options.exact!==void 0?{exact:!!options.exact}:void 0;switch(method){case"getByLabel":return page.getByLabel(first,exactOpts).first();case"getByText":return page.getByText(first,exactOpts).first();case"getByPlaceholder":return page.getByPlaceholder(first,exactOpts).first();case"getByAltText":return page.getByAltText(first,exactOpts).first();case"getByTitle":return page.getByTitle(first,exactOpts).first();default:return null}}function parseRef(arg){let s=arg.trim();return s.startsWith("@")?s.slice(1):s.toLowerCase().startsWith("ref=")?s.slice(4):/^e\d+$/.test(s)?s:null}function getLocatorFromRef(page,refMap,ref){let data=refMap[ref];if(!data)return null;if(data.role==="clickable"||data.role==="focusable"){let locator2=page.locator(data.selector);return data.nth!==void 0?locator2.nth(data.nth):locator2.first()}let locator;return data.name!==void 0&&data.name!==""?locator=page.getByRole(data.role,{name:data.name,exact:!0}):locator=page.getByRole(data.role),data.nth!==void 0?locator.nth(data.nth):locator.first()}function assertRefRole(context,selectorOrRef,allowedRoles,actionLabel,hint){let ref=parseRef(selectorOrRef),refMap=context.getRefMap();if(!ref||!refMap[ref])return;let entry=refMap[ref];if(allowedRoles.has(entry.role))return;let namePart=entry.name?` "${entry.name}"`:"";throw new Error(`Ref ${ref} points to a ${entry.role}${namePart}, which is not valid for ${actionLabel}. ${hint}`)}function resolveSelectorOrRef(context,selectorOrRef){let ref=parseRef(selectorOrRef),refMap=context.getRefMap();if(ref&&Object.keys(refMap).length>0){let locator=getLocatorFromRef(context.page,refMap,ref);if(locator)return locator}let playwrightLocator=tryParsePlaywrightLocatorExpression(context.page,selectorOrRef);return playwrightLocator||context.page.locator(selectorOrRef).first()}import jpegjs from"jpeg-js";import{PNG}from"pngjs";var ANNOTATION_OVERLAY_ID="__browser_devtools_mcp_annotations__",ANNOTATE_BOX_TIMEOUT_MS=2e3,ScreenshotType=(ScreenshotType2=>(ScreenshotType2.PNG="png",ScreenshotType2.JPEG="jpeg",ScreenshotType2))(ScreenshotType||{}),MAX_BUFFER_SIZE=800*1024,MAX_PIXELS=1.15*1024*1024,MAX_LINEAR_SIZE=1568;function scaleImageToSize(image,size){let{data:src,width:w1,height:h1}=image,w2=Math.max(1,Math.floor(size.width)),h2=Math.max(1,Math.floor(size.height));if(w1===w2&&h1===h2)return image;if(w1<=0||h1<=0)throw new Error("Invalid input image");if(size.width<=0||size.height<=0||!isFinite(size.width)||!isFinite(size.height))throw new Error("Invalid output dimensions");let clamp=(v,lo,hi)=>v<lo?lo:v>hi?hi:v,weights=(t,o)=>{let t2=t*t,t3=t2*t;o[0]=-.5*t+1*t2-.5*t3,o[1]=1-2.5*t2+1.5*t3,o[2]=.5*t+2*t2-1.5*t3,o[3]=-.5*t2+.5*t3},srcRowStride=w1*4,dstRowStride=w2*4,xOff=new Int32Array(w2*4),xW=new Float32Array(w2*4),wx=new Float32Array(4),xScale=w1/w2;for(let x=0;x<w2;x++){let sx=(x+.5)*xScale-.5,sxi=Math.floor(sx),t=sx-sxi;weights(t,wx);let b=x*4,i0=clamp(sxi-1,0,w1-1),i1=clamp(sxi+0,0,w1-1),i2=clamp(sxi+1,0,w1-1),i3=clamp(sxi+2,0,w1-1);xOff[b+0]=i0<<2,xOff[b+1]=i1<<2,xOff[b+2]=i2<<2,xOff[b+3]=i3<<2,xW[b+0]=wx[0],xW[b+1]=wx[1],xW[b+2]=wx[2],xW[b+3]=wx[3]}let yRow=new Int32Array(h2*4),yW=new Float32Array(h2*4),wy=new Float32Array(4),yScale=h1/h2;for(let y=0;y<h2;y++){let sy=(y+.5)*yScale-.5,syi=Math.floor(sy),t=sy-syi;weights(t,wy);let b=y*4,j0=clamp(syi-1,0,h1-1),j1=clamp(syi+0,0,h1-1),j2=clamp(syi+1,0,h1-1),j3=clamp(syi+2,0,h1-1);yRow[b+0]=j0*srcRowStride,yRow[b+1]=j1*srcRowStride,yRow[b+2]=j2*srcRowStride,yRow[b+3]=j3*srcRowStride,yW[b+0]=wy[0],yW[b+1]=wy[1],yW[b+2]=wy[2],yW[b+3]=wy[3]}let dst=new Uint8Array(w2*h2*4);for(let y=0;y<h2;y++){let yb=y*4,rb0=yRow[yb+0],rb1=yRow[yb+1],rb2=yRow[yb+2],rb3=yRow[yb+3],wy0=yW[yb+0],wy1=yW[yb+1],wy2=yW[yb+2],wy3=yW[yb+3],dstBase=y*dstRowStride;for(let x=0;x<w2;x++){let xb=x*4,xo0=xOff[xb+0],xo1=xOff[xb+1],xo2=xOff[xb+2],xo3=xOff[xb+3],wx0=xW[xb+0],wx1=xW[xb+1],wx2=xW[xb+2],wx3=xW[xb+3],di=dstBase+(x<<2);for(let c=0;c<4;c++){let r0=src[rb0+xo0+c]*wx0+src[rb0+xo1+c]*wx1+src[rb0+xo2+c]*wx2+src[rb0+xo3+c]*wx3,r1=src[rb1+xo0+c]*wx0+src[rb1+xo1+c]*wx1+src[rb1+xo2+c]*wx2+src[rb1+xo3+c]*wx3,r2=src[rb2+xo0+c]*wx0+src[rb2+xo1+c]*wx1+src[rb2+xo2+c]*wx2+src[rb2+xo3+c]*wx3,r3=src[rb3+xo0+c]*wx0+src[rb3+xo1+c]*wx1+src[rb3+xo2+c]*wx2+src[rb3+xo3+c]*wx3,v=r0*wy0+r1*wy1+r2*wy2+r3*wy3;dst[di+c]=v<0?0:v>255?255:v|0}}}return{data:Buffer.from(dst.buffer),width:w2,height:h2}}function scaleImageToFitMessage(buffer,screenshotType){let image=screenshotType==="png"?PNG.sync.read(buffer):jpegjs.decode(buffer,{maxMemoryUsageInMB:512}),pixels=image.width*image.height,shrink=Math.min(MAX_LINEAR_SIZE/image.width,MAX_LINEAR_SIZE/image.height,Math.sqrt(MAX_PIXELS/pixels));shrink>1&&(shrink=1);let width=image.width*shrink|0,height=image.height*shrink|0,scaledImage=scaleImageToSize(image,{width,height}),quality=screenshotType==="png"?75:70,result=screenshotType==="png"?jpegjs.encode(scaledImage,quality).data:jpegjs.encode(scaledImage,quality).data,iterations=0,MAX_ITERATIONS=5;for(;result.length>MAX_BUFFER_SIZE&&iterations<MAX_ITERATIONS;)quality=Math.max(50,quality-10),quality<=50&&result.length>MAX_BUFFER_SIZE&&(shrink*=.85,width=Math.max(200,image.width*shrink|0),height=Math.max(200,image.height*shrink|0),scaledImage=scaleImageToSize(image,{width,height})),result=jpegjs.encode(scaledImage,quality).data,iterations++;return result}async function captureScreenshot(context,options){let fullPage=options.fullPage??!1,screenshotType=options.type??"png",annotate=options.annotate??!1,annotateContent=options.annotateContent??!1,annotateCursorInteractive=options.annotateCursorInteractive??!1,quality=options.quality??(screenshotType==="png"?void 0:100),overlayInjected=!1,annotations,scopedElement=null;if(options.selector&&(scopedElement=await resolveSelectorOrRef(context,options.selector).elementHandle({timeout:1e4}),!scopedElement))throw new Error(`Element not found: ${options.selector}`);if(annotate){let refMap=context.getRefMap(),wantContent=annotateContent,wantCursorInteractive=annotateCursorInteractive;if(Object.keys(refMap).length===0||wantCursorInteractive||wantContent){let raw=await context.page.locator("body").ariaSnapshot(),{refs}=processAriaTreeWithRefs(raw,{interactiveOnly:!wantContent}),mergedRefs={...refs};if(wantCursorInteractive){let cursorEntries=await findCursorInteractiveElements(context.page,null);if(cursorEntries.length>0){let maxNum=Object.keys(mergedRefs).reduce((m,k)=>Math.max(m,parseInt(k.replace(/^e/,""),10)||0),0);cursorEntries.forEach((entry,i)=>{mergedRefs[`e${maxNum+1+i}`]={role:entry.role,name:entry.name,selector:entry.selector,nth:entry.nth}})}}context.setRefMap(mergedRefs),refMap=mergedRefs}let entries=Object.entries(refMap),allBoxes=[];for(let[ref,data]of entries){let locator=getLocatorFromRef(context.page,refMap,ref);if(!locator)continue;let box=await locator.boundingBox({timeout:ANNOTATE_BOX_TIMEOUT_MS}).catch(()=>null);if(!box||box.width===0||box.height===0)continue;let num=parseInt(ref.replace(/^e/,""),10)||0;allBoxes.push({ref,number:num,role:data.role,name:data.name,box:{x:Math.round(box.x),y:Math.round(box.y),width:Math.round(box.width),height:Math.round(box.height)}})}allBoxes.sort((a,b)=>a.number-b.number);let boxes=allBoxes,overlayBoxesViewport=allBoxes;if(scopedElement){let elementBox=await scopedElement.boundingBox();if(elementBox&&elementBox.width>0&&elementBox.height>0){let overlapping=allBoxes.filter(a=>{let b=a.box;return b.x<elementBox.x+elementBox.width&&b.x+b.width>elementBox.x&&b.y<elementBox.y+elementBox.height&&b.y+b.height>elementBox.y});overlayBoxesViewport=overlapping,boxes=overlapping.map(a=>{let b=a.box,relX=Math.max(0,b.x-elementBox.x),relY=Math.max(0,b.y-elementBox.y),relRight=Math.min(b.x+b.width-elementBox.x,elementBox.width),relBottom=Math.min(b.y+b.height-elementBox.y,elementBox.height);return{...a,box:{x:Math.round(relX),y:Math.round(relY),width:Math.round(Math.max(0,relRight-relX)),height:Math.round(Math.max(0,relBottom-relY))}}})}}if(boxes.length>0){let overlayPayload={data:overlayBoxesViewport.map(a=>({number:a.number,x:a.box.x,y:a.box.y,width:a.box.width,height:a.box.height})),id:ANNOTATION_OVERLAY_ID};if(await context.page.evaluate(({data,id})=>{let sx=window.scrollX||0,sy=window.scrollY||0,c=document.createElement("div");c.id=id,c.style.cssText="position:absolute;top:0;left:0;width:0;height:0;pointer-events:none;z-index:2147483647;";for(let it of data){let dx=it.x+sx,dy=it.y+sy,b=document.createElement("div");b.style.cssText=`position:absolute;left:${dx}px;top:${dy}px;width:${it.width}px;height:${it.height}px;border:2px solid rgba(255,0,0,0.8);box-sizing:border-box;pointer-events:none;`;let l=document.createElement("div");l.textContent=String(it.number);let labelTop=dy<14?"2px":"-14px";l.style.cssText=`position:absolute;top:${labelTop};left:-2px;background:rgba(255,0,0,0.9);color:#fff;font:bold 11px/14px monospace;padding:0 4px;border-radius:2px;white-space:nowrap;`,b.appendChild(l),c.appendChild(b)}document.documentElement.appendChild(c)},overlayPayload),overlayInjected=!0,fullPage&&!scopedElement&&boxes.length>0){let scroll=await context.page.evaluate(()=>({x:window.scrollX||0,y:window.scrollY||0}));annotations=boxes.map(a=>({...a,box:{x:a.box.x+scroll.x,y:a.box.y+scroll.y,width:a.box.width,height:a.box.height}}))}else annotations=boxes}}try{let screenshotOptions={type:screenshotType,fullPage,quality};scopedElement&&(screenshotOptions.element=scopedElement);let rawBuffer=await context.page.screenshot(screenshotOptions),scaledBuffer=scaleImageToFitMessage(rawBuffer,screenshotType);return{rawBuffer,image:{data:scaledBuffer,mimeType:`image/${screenshotType}`},annotations,screenshotType}}finally{overlayInjected&&await context.page.evaluate(id=>{let el=document.getElementById(id);el&&el.remove()},ANNOTATION_OVERLAY_ID).catch(()=>{})}}import fs7 from"node:fs/promises";import os4 from"node:os";import path5 from"node:path";import{z as z11}from"zod";var DEFAULT_SCREENSHOT_NAME="screenshot",DEFAULT_SCREENSHOT_TYPE="png",DEFAULT_SCREENSHOT_QUALITY=100,TakeScreenshot=class{name(){return"content_take-screenshot"}description(){return`
|
|
275
275
|
Takes a screenshot of the current page or a specific element.
|
|
276
276
|
Do NOT use for page structure\u2014use ARIA/AX snapshots instead.
|
|
277
277
|
Use only for visual verification (design check, visual bug, contrast, layout).
|
|
278
278
|
Screenshot is saved to disk; use includeBase64 only when the file cannot be read from the returned path (e.g. remote, container).
|
|
279
|
-
`.trim()}inputSchema(){return{outputPath:z11.string().optional().default(os4.tmpdir()),name:z11.string().optional().default(DEFAULT_SCREENSHOT_NAME),selector:z11.string().optional().describe("Selector/ref; omit=viewport."),fullPage:z11.boolean().optional().default(!1),type:z11.enum(getEnumKeyTuples(ScreenshotType)).transform(createEnumTransformer(ScreenshotType)).optional().default(DEFAULT_SCREENSHOT_TYPE),quality:z11.number().int().min(0).max(DEFAULT_SCREENSHOT_QUALITY).optional().describe("JPEG quality 0\u2013100."),includeBase64:z11.boolean().optional().default(!1).describe("Base64 fallback."),annotate:z11.boolean().optional().default(!1).describe("Overlay ARIA ref labels."),annotateContent:z11.boolean().optional().default(!1).describe("Annotate headings."),annotateCursorInteractive:z11.boolean().optional().default(!1).describe("Annotate cursor:pointer elements.")}}outputSchema(){return{filePath:z11.string().describe("Full path of the saved screenshot file."),image:z11.object({data:z11.any().describe("Base64-encoded image data."),mimeType:z11.string().describe("MIME type of the image.")}).optional().describe('Image data included only when "includeBase64" input parameter is set to true.'),annotations:z11.array(z11.object({ref:z11.string(),number:z11.number(),role:z11.string(),name:z11.string().optional(),box:z11.object({x:z11.number(),y:z11.number(),width:z11.number(),height:z11.number()})})).optional().describe("When annotate is true, list of refs and bounding boxes. When selector is set: only annotations overlapping the element, box relative to that element. When fullPage is true: box is document-relative (matches the full-page image). Otherwise: viewport-relative.")}}async handle(context,args){let screenshotType=args.type??DEFAULT_SCREENSHOT_TYPE,filename=`${args.name??DEFAULT_SCREENSHOT_NAME}-${formattedTimeForFilename()}.${screenshotType}`,filePath=path5.resolve(args.outputPath,filename),result=await captureScreenshot(context,{fullPage:args.fullPage,type:screenshotType,annotate:args.annotate,annotateContent:args.annotateContent,annotateCursorInteractive:args.annotateCursorInteractive,selector:args.selector,quality:args.quality});await
|
|
279
|
+
`.trim()}inputSchema(){return{outputPath:z11.string().optional().default(os4.tmpdir()),name:z11.string().optional().default(DEFAULT_SCREENSHOT_NAME),selector:z11.string().optional().describe("Selector/ref; omit=viewport."),fullPage:z11.boolean().optional().default(!1),type:z11.enum(getEnumKeyTuples(ScreenshotType)).transform(createEnumTransformer(ScreenshotType)).optional().default(DEFAULT_SCREENSHOT_TYPE),quality:z11.number().int().min(0).max(DEFAULT_SCREENSHOT_QUALITY).optional().describe("JPEG quality 0\u2013100."),includeBase64:z11.boolean().optional().default(!1).describe("Base64 fallback."),annotate:z11.boolean().optional().default(!1).describe("Overlay ARIA ref labels."),annotateContent:z11.boolean().optional().default(!1).describe("Annotate headings."),annotateCursorInteractive:z11.boolean().optional().default(!1).describe("Annotate cursor:pointer elements.")}}outputSchema(){return{filePath:z11.string().describe("Full path of the saved screenshot file."),image:z11.object({data:z11.any().describe("Base64-encoded image data."),mimeType:z11.string().describe("MIME type of the image.")}).optional().describe('Image data included only when "includeBase64" input parameter is set to true.'),annotations:z11.array(z11.object({ref:z11.string(),number:z11.number(),role:z11.string(),name:z11.string().optional(),box:z11.object({x:z11.number(),y:z11.number(),width:z11.number(),height:z11.number()})})).optional().describe("When annotate is true, list of refs and bounding boxes. When selector is set: only annotations overlapping the element, box relative to that element. When fullPage is true: box is document-relative (matches the full-page image). Otherwise: viewport-relative.")}}async handle(context,args){let screenshotType=args.type??DEFAULT_SCREENSHOT_TYPE,filename=`${args.name??DEFAULT_SCREENSHOT_NAME}-${formattedTimeForFilename()}.${screenshotType}`,filePath=path5.resolve(args.outputPath,filename),result=await captureScreenshot(context,{fullPage:args.fullPage,type:screenshotType,annotate:args.annotate,annotateContent:args.annotateContent,annotateCursorInteractive:args.annotateCursorInteractive,selector:args.selector,quality:args.quality});await fs7.mkdir(path5.dirname(filePath),{recursive:!0}),await fs7.writeFile(filePath,result.rawBuffer);let output={filePath,_artifacts:[{type:"IMAGE",filePath,mimeType:result.image.mimeType}],...result.annotations&&result.annotations.length>0?{annotations:result.annotations}:{}};return args.includeBase64&&(output.image=result.image),output}};var tools2=[new GetAsHtml,new GetAsText,new SaveAsPdf,new StartRecording,new StopRecording,new TakeScreenshot];import{z as z12}from"zod";var DEFAULT_DEBUG_CONFIG={maxSnapshots:1e3,maxCallStackDepth:1,maxFramesWithScopes:1,maxAsyncStackSegments:10,maxFramesPerAsyncSegment:10},STORE_BY_CONTEXT=new WeakMap;function _generateId(){let t=Date.now(),r=Math.floor(Math.random()*1e6);return`${t.toString(36)}-${r.toString(36)}`}function _columnForLocationKey(column){return column==null||column===0?1:column}function _locationKey(urlPattern,lineNumber,columnNumber){return`${urlPattern}:${lineNumber}:${columnNumber}`}function _evaluateHitCondition(hitCondition,hitCount){try{let condition=hitCondition.trim();return/^[=<>!%]/.test(condition)&&(condition=`hitCount ${condition}`),!!new Function("hitCount",`return (${condition});`)(hitCount)}catch{return!1}}async function _evaluateWatchExpressionsOnFrame(v8Api,callFrameId,watchExpressions){let results={};for(let watch of watchExpressions.values())try{let result=await v8Api.evaluateOnCallFrame(callFrameId,watch.expression);if(result.exceptionDetails)results[watch.expression]=`[Error: ${result.exceptionDetails.text||"Evaluation failed"}]`;else{let value=await v8Api.extractValueDeep(result.result,2);results[watch.expression]=value}}catch(e){results[watch.expression]=`[Error: ${e.message||"Unknown error"}]`}return results}function _ensureStore(ctx,page,v8Options,config){let existing=STORE_BY_CONTEXT.get(ctx);if(existing)return existing;let v8Api=new V8Api(page,v8Options),sourceMapResolver=new SourceMapResolver(page),mergedConfig={...DEFAULT_DEBUG_CONFIG,...config},store={v8Api,sourceMapResolver,probes:new Map,locationIndex:new Map,watchExpressions:new Map,snapshots:[],snapshotSequence:0,config:mergedConfig,enabled:!1,sourceMapsLoaded:!1,exceptionBreakpoint:"none"};return STORE_BY_CONTEXT.set(ctx,store),store}function _getStore(ctx){return STORE_BY_CONTEXT.get(ctx)}async function _captureScopes(v8Api,callFrame,maxDepth=3){let scopeSnapshots=[];for(let scope of callFrame.scopeChain)if(!(scope.type==="global"||scope.type==="script"||scope.type==="with"||scope.type==="eval"||scope.type==="wasm-expression-stack")){if(scopeSnapshots.length>=maxDepth)break;try{let variables=await v8Api.getScopeVariables(scope),scopeVariables=[];for(let[name,value]of Object.entries(variables))scopeVariables.push({name,value,type:typeof value});scopeSnapshots.push({type:scope.type,name:scope.name,variables:scopeVariables})}catch{}}return scopeSnapshots}async function _callFrameToSnapshot(v8Api,frame,captureScopes,sourceMapResolver){let scopes=[];captureScopes&&(scopes=await _captureScopes(v8Api,frame));let originalLocation;if(sourceMapResolver){let resolved=sourceMapResolver.generatedToOriginal(frame.location.scriptId,frame.location.lineNumber,frame.location.columnNumber??0);resolved&&(originalLocation={source:resolved.source,line:resolved.line+1,column:resolved.column!==void 0?resolved.column+1:void 0,name:resolved.name})}return{functionName:frame.functionName||"(anonymous)",url:frame.url||"",lineNumber:frame.location.lineNumber+1,columnNumber:frame.location.columnNumber!==void 0?frame.location.columnNumber+1:void 0,scriptId:frame.location.scriptId,scopes,originalLocation}}function _convertAsyncStackTrace(asyncTrace,sourceMapResolver,maxSegments,maxFramesPerSegment){if(!asyncTrace)return;let maxSegs=maxSegments??DEFAULT_DEBUG_CONFIG.maxAsyncStackSegments,maxFrames=maxFramesPerSegment??DEFAULT_DEBUG_CONFIG.maxFramesPerAsyncSegment,segments=[],currentTrace=asyncTrace,segmentCount=0;for(;currentTrace&&segmentCount<maxSegs;){let asyncFrames=[];for(let frame of currentTrace.callFrames.slice(0,maxFrames)){let originalLocation;if(sourceMapResolver){let resolved=sourceMapResolver.generatedToOriginal(frame.location.scriptId,frame.location.lineNumber,frame.location.columnNumber??0);resolved&&(originalLocation={source:resolved.source,line:resolved.line+1,column:resolved.column!==void 0?resolved.column+1:void 0,name:resolved.name})}asyncFrames.push({functionName:frame.functionName||"(anonymous)",url:frame.url||"",lineNumber:frame.location.lineNumber+1,columnNumber:frame.location.columnNumber!==void 0?frame.location.columnNumber+1:void 0,originalLocation})}asyncFrames.length>0&&segments.push({description:currentTrace.description,callFrames:asyncFrames}),currentTrace=currentTrace.parent,segmentCount++}if(segments.length!==0)return{segments}}async function enableDebugging2(ctx,page,options){let store=_ensureStore(ctx,page,options?.v8Options,options?.config);if(!store.enabled){await store.v8Api.enable(),store.v8Api.on("scriptParsed",script=>{store.sourceMapResolver.registerScript(script)});for(let script of store.v8Api.getScripts())store.sourceMapResolver.registerScript(script);store.v8Api.on("paused",async event=>{let startTime=Date.now();try{let isException=event.reason==="exception"||event.reason==="promiseRejection",hitBreakpointIds=event.hitBreakpoints||[],hitProbes=[];for(let probe of store.probes.values()){if(!probe.enabled)continue;if(probe.v8BreakpointIds.some(id=>hitBreakpointIds.includes(id))){let conditionMet=!0;probe.hitCondition&&(conditionMet=_evaluateHitCondition(probe.hitCondition,probe.hitCount+1)),hitProbes.push({probe,conditionMet})}}let shouldCaptureBreakpoint=hitProbes.some(p=>p.conditionMet),shouldCaptureException=isException&&store.exceptionBreakpoint!=="none";for(let{probe}of hitProbes)probe.hitCount++,probe.lastHitAt=Date.now();if((shouldCaptureBreakpoint||shouldCaptureException)&&event.callFrames.length>0){let topFrame=event.callFrames[0],exceptionInfo;if(isException&&event.data){let excData=event.data;exceptionInfo={type:event.reason==="promiseRejection"?"promiseRejection":"exception",message:excData.description||excData.value||String(excData),name:excData.className,stack:excData.description}}let originalLocation,resolvedLoc=store.sourceMapResolver.generatedToOriginal(topFrame.location.scriptId,topFrame.location.lineNumber,topFrame.location.columnNumber??0);resolvedLoc&&(originalLocation={source:resolvedLoc.source,line:resolvedLoc.line+1,column:resolvedLoc.column!==void 0?resolvedLoc.column+1:void 0,name:resolvedLoc.name});let baseSnapshot={url:topFrame.url||"",lineNumber:topFrame.location.lineNumber+1,columnNumber:topFrame.location.columnNumber!==void 0?topFrame.location.columnNumber+1:void 0,originalLocation,exception:exceptionInfo,captureTimeMs:Date.now()-startTime},callStackFull=[],framesToProcess=event.callFrames.slice(0,store.config.maxCallStackDepth);for(let i=0;i<framesToProcess.length;i++){let frame=framesToProcess[i],captureScopes=i<store.config.maxFramesWithScopes;callStackFull.push(await _callFrameToSnapshot(store.v8Api,frame,captureScopes,store.sourceMapResolver))}let asyncStackTraceFull=_convertAsyncStackTrace(event.asyncStackTrace,store.sourceMapResolver,store.config.maxAsyncStackSegments,store.config.maxFramesPerAsyncSegment),probesToCapture=hitProbes.filter(p=>p.conditionMet),hasException=shouldCaptureException,snapshotCount=probesToCapture.length+(hasException?1:0);for(let s=0;s<snapshotCount;s++){let probeId,isLogpoint,logResult,callStack,asyncStackTrace,watchResults;if(s<probesToCapture.length){let{probe}=probesToCapture[s];if(probeId=probe.id,isLogpoint=probe.kind==="logpoint",isLogpoint&&probe.logExpression)try{let wrappedExpression=`(
|
|
280
280
|
${probe.logExpression}
|
|
281
281
|
)`,evalResult=await store.v8Api.evaluateOnCallFrame(topFrame.callFrameId,wrappedExpression,{returnByValue:!0,generatePreview:!0});logResult=store.v8Api.extractValue(evalResult.result)}catch{logResult="[evaluation error]"}else logResult=void 0;isLogpoint?(callStack=[],asyncStackTrace=void 0,watchResults=void 0):(callStack=callStackFull,asyncStackTrace=asyncStackTraceFull,watchResults=store.watchExpressions.size>0?await _evaluateWatchExpressionsOnFrame(store.v8Api,topFrame.callFrameId,store.watchExpressions):void 0)}else probeId="__exception__",isLogpoint=!1,logResult=void 0,callStack=callStackFull,asyncStackTrace=asyncStackTraceFull,watchResults=store.watchExpressions.size>0?await _evaluateWatchExpressionsOnFrame(store.v8Api,topFrame.callFrameId,store.watchExpressions):void 0;let snapshot={id:_generateId(),probeId,timestamp:Date.now(),sequenceNumber:++store.snapshotSequence,...baseSnapshot,callStack,asyncStackTrace,logResult,watchResults};store.snapshots.push(snapshot)}store.snapshots.length>store.config.maxSnapshots&&store.snapshots.splice(0,store.snapshots.length-store.config.maxSnapshots)}}finally{await store.v8Api.resume()}}),store.enabled=!0,store.sourceMapResolver.loadAllSourceMaps().then(()=>{store.sourceMapsLoaded=!0}).catch(()=>{})}}function isDebuggingEnabled2(ctx){return _getStore(ctx)?.enabled??!1}async function resolveSourceLocation2(ctx,page,url,line,column=1){let store=_ensureStore(ctx,page);store.enabled||await enableDebugging2(ctx,page);let resolved=await store.sourceMapResolver.resolveLocationByUrl(url,line,column);return resolved?{source:resolved.source,line:resolved.line+1,column:resolved.column+1,name:resolved.name}:null}async function setExceptionBreakpoint2(ctx,state){let store=_getStore(ctx);if(!store||!store.enabled)throw new Error("Debugging is not enabled");await store.v8Api.setPauseOnExceptions(state),store.exceptionBreakpoint=state}function getExceptionBreakpoint2(ctx){return _getStore(ctx)?.exceptionBreakpoint??"none"}function hasSourceMaps2(ctx){return _getStore(ctx)?.sourceMapResolver.hasSourceMaps()??!1}async function createProbe2(ctx,options){let store=_getStore(ctx);if(!store||!store.enabled)throw new Error("Debugging is not enabled");let probeId=_generateId(),columnForKey=_columnForLocationKey(options.columnNumber),locationKey=_locationKey(options.urlPattern,options.lineNumber,columnForKey),existingEntry=store.locationIndex.get(locationKey);if(existingEntry){existingEntry.refCount++;let probe2={id:probeId,kind:options.kind,enabled:!0,urlPattern:options.urlPattern,lineNumber:options.lineNumber,columnNumber:options.columnNumber,condition:options.condition,logExpression:options.logExpression,hitCondition:options.hitCondition,v8BreakpointIds:[existingEntry.breakpointId],resolvedLocations:existingEntry.resolvedLocations,hitCount:0,createdAt:Date.now()};return store.probes.set(probeId,probe2),probe2}let fullCondition;options.condition?fullCondition=`(${options.condition})`:fullCondition="true";let line0based=options.lineNumber-1,column0based=columnForKey-1,resolved=store.sourceMapResolver.originalToGenerated(options.urlPattern,line0based,column0based),breakpointId,resolvedLocationsCount=0;if(resolved)try{breakpointId=(await store.v8Api.setBreakpoint({scriptId:resolved.scriptId,lineNumber:resolved.location.line,columnNumber:resolved.location.column},fullCondition)).breakpointId,resolvedLocationsCount=1}catch{let scriptUrl=store.sourceMapResolver.getScriptUrl(resolved.scriptId);if(scriptUrl){let result=await store.v8Api.setBreakpointByUrl({url:scriptUrl,lineNumber:resolved.location.line,columnNumber:resolved.location.column,condition:fullCondition});breakpointId=result.breakpointId,resolvedLocationsCount=result.locations.length}else throw new Error("Failed to set breakpoint at resolved location and could not fall back (script URL unknown). A probe may already exist at this line; remove it first or use a different line.")}else{let urlRegex=options.urlPattern.replace(/\\([.*+?^${}()|[\]\\/-])/g,"$1").replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,".*").replace(/\\\?/g,"."),result=await store.v8Api.setBreakpointByUrl({urlRegex,lineNumber:options.lineNumber-1,columnNumber:options.columnNumber!=null?Math.max(0,options.columnNumber-1):void 0,condition:fullCondition});breakpointId=result.breakpointId,resolvedLocationsCount=result.locations.length}store.locationIndex.set(locationKey,{breakpointId,resolvedLocations:resolvedLocationsCount,refCount:1});let probe={id:probeId,kind:options.kind,enabled:!0,urlPattern:options.urlPattern,lineNumber:options.lineNumber,columnNumber:options.columnNumber,condition:options.condition,logExpression:options.logExpression,hitCondition:options.hitCondition,v8BreakpointIds:[breakpointId],resolvedLocations:resolvedLocationsCount,hitCount:0,createdAt:Date.now()};return store.probes.set(probeId,probe),probe}async function removeProbe2(ctx,probeId){let store=_getStore(ctx);if(!store)return!1;let probe=store.probes.get(probeId);if(!probe)return!1;let locationKey=_locationKey(probe.urlPattern,probe.lineNumber,_columnForLocationKey(probe.columnNumber)),entry=store.locationIndex.get(locationKey);if(entry&&(entry.refCount--,entry.refCount===0)){try{await store.v8Api.removeBreakpoint(entry.breakpointId)}catch{}store.locationIndex.delete(locationKey)}return store.probes.delete(probeId),!0}function listProbes2(ctx){let store=_getStore(ctx);return store?Array.from(store.probes.values()):[]}function getProbe(ctx,probeId){let store=_getStore(ctx);if(store)return store.probes.get(probeId)}function getSnapshots2(ctx){let store=_getStore(ctx);return store?[...store.snapshots]:[]}function getSnapshotsByProbe2(ctx,probeId){let store=_getStore(ctx);return store?store.snapshots.filter(s=>s.probeId===probeId):[]}function clearSnapshotsByProbe(ctx,probeId){let store=_getStore(ctx);if(!store)return 0;let before=store.snapshots.length;return store.snapshots=store.snapshots.filter(s=>s.probeId!==probeId),before-store.snapshots.length}function getSnapshotStats2(ctx){let store=_getStore(ctx);if(!store||store.snapshots.length===0)return{totalSnapshots:0,snapshotsByProbe:{},averageCaptureTimeMs:0};let snapshotsByProbe={},totalCaptureTime=0;for(let snapshot of store.snapshots)snapshotsByProbe[snapshot.probeId]=(snapshotsByProbe[snapshot.probeId]||0)+1,totalCaptureTime+=snapshot.captureTimeMs;return{totalSnapshots:store.snapshots.length,snapshotsByProbe,oldestTimestamp:store.snapshots[0].timestamp,newestTimestamp:store.snapshots[store.snapshots.length-1].timestamp,averageCaptureTimeMs:totalCaptureTime/store.snapshots.length}}function addWatchExpression2(ctx,expression){let store=_getStore(ctx);if(!store)throw new Error("Debug store not initialized");let id=_generateId(),watchExpr={id,expression,createdAt:Date.now()};return store.watchExpressions.set(id,watchExpr),watchExpr}function removeWatchExpression2(ctx,watchExpressionId){let store=_getStore(ctx);return store?store.watchExpressions.delete(watchExpressionId):!1}function listWatchExpressions2(ctx){let store=_getStore(ctx);return store?Array.from(store.watchExpressions.values()):[]}function clearWatchExpressions2(ctx){let store=_getStore(ctx);if(!store)return 0;let count=store.watchExpressions.size;return store.watchExpressions.clear(),count}var Status=class{name(){return"debug_status"}description(){return`
|
|
282
282
|
Returns the current debugging status including:
|
|
@@ -476,7 +476,7 @@ Use this tool to:
|
|
|
476
476
|
- Jump to the top/bottom without knowing exact positions
|
|
477
477
|
- Bring elements into view before clicking
|
|
478
478
|
- Inspect lazy-loaded content that appears on scroll
|
|
479
|
-
`.trim()}inputSchema(){return{mode:z33.enum(["by","to","top","bottom","left","right"]).optional().default(DEFAULT_MODE).describe("by=dx,dy; to=x,y; or edge (top/bottom/left/right)."),selector:z33.string().optional().describe("Scrollable container: ref (e.g. from latest <a11y_take-aria-snapshot>), getBy\u2026 expression, or CSS. Omit for viewport."),dx:z33.number().optional(),dy:z33.number().optional(),x:z33.number().optional(),y:z33.number().optional(),behavior:z33.enum(["auto","smooth"]).optional().default(DEFAULT_BEHAVIOR)}}outputSchema(){return{mode:z33.enum(["by","to","top","bottom","left","right"]).describe("The scroll mode used."),selector:z33.string().nullable().describe("The selector of the scroll container if provided; otherwise null (document viewport)."),behavior:z33.enum(["auto","smooth"]).describe("The scroll behavior used."),before:z33.object({x:z33.number().describe("ScrollLeft before scrolling."),y:z33.number().describe("ScrollTop before scrolling."),scrollWidth:z33.number().describe("Total scrollable width before scrolling."),scrollHeight:z33.number().describe("Total scrollable height before scrolling."),clientWidth:z33.number().describe("Viewport/container client width before scrolling."),clientHeight:z33.number().describe("Viewport/container client height before scrolling.")}).describe("Scroll metrics before the scroll action."),after:z33.object({x:z33.number().describe("ScrollLeft after scrolling."),y:z33.number().describe("ScrollTop after scrolling."),scrollWidth:z33.number().describe("Total scrollable width after scrolling."),scrollHeight:z33.number().describe("Total scrollable height after scrolling."),clientWidth:z33.number().describe("Viewport/container client width after scrolling."),clientHeight:z33.number().describe("Viewport/container client height after scrolling.")}).describe("Scroll metrics after the scroll action."),canScrollX:z33.boolean().describe("Whether horizontal scrolling is possible (scrollWidth > clientWidth)."),canScrollY:z33.boolean().describe("Whether vertical scrolling is possible (scrollHeight > clientHeight)."),maxScrollX:z33.number().describe("Maximum horizontal scrollLeft (scrollWidth - clientWidth)."),maxScrollY:z33.number().describe("Maximum vertical scrollTop (scrollHeight - clientHeight)."),isAtLeft:z33.boolean().describe("Whether the scroll position is at the far left."),isAtRight:z33.boolean().describe("Whether the scroll position is at the far right."),isAtTop:z33.boolean().describe("Whether the scroll position is at the very top."),isAtBottom:z33.boolean().describe("Whether the scroll position is at the very bottom.")}}async handle(context,args){let mode=args.mode??DEFAULT_MODE,selector=args.selector,behavior=args.behavior??DEFAULT_BEHAVIOR,dx=args.dx??0,dy=args.dy??0,x=args.x,y=args.y;if(mode==="to"&&typeof x!="number"&&typeof y!="number")throw new Error('mode="to" requires at least one of x or y.');if(mode==="by"&&dx===0&&dy===0)throw new Error('mode="by" requires dx and/or dy to be non-zero.');let params={modeEval:mode,selectorEval:selector,dxEval:dx,dyEval:dy,xEval:x,yEval:y,behaviorEval:behavior},result;return selector?result=await resolveSelectorOrRef(context,selector).evaluate((el,p)=>{let before={x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight},readMetrics=elem2=>({x:elem2.scrollLeft,y:elem2.scrollTop,scrollWidth:elem2.scrollWidth,scrollHeight:elem2.scrollHeight,clientWidth:elem2.clientWidth,clientHeight:elem2.clientHeight}),clamp=(v,min,max)=>Math.max(min,Math.min(max,v)),elem=el,maxX=Math.max(0,elem.scrollWidth-elem.clientWidth),maxY=Math.max(0,elem.scrollHeight-elem.clientHeight);p.modeEval==="by"?elem.scrollTo({left:clamp(elem.scrollLeft+p.dxEval,0,maxX),top:clamp(elem.scrollTop+p.dyEval,0,maxY),behavior:p.behaviorEval}):p.modeEval==="to"?elem.scrollTo({left:typeof p.xEval=="number"?clamp(p.xEval,0,maxX):elem.scrollLeft,top:typeof p.yEval=="number"?clamp(p.yEval,0,maxY):elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="top"?elem.scrollTo({top:0,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="bottom"?elem.scrollTo({top:maxY,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="left"?elem.scrollTo({left:0,top:elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="right"&&elem.scrollTo({left:maxX,top:elem.scrollTop,behavior:p.behaviorEval});let after=readMetrics(elem);return{before,after,canScrollX:after.scrollWidth>after.clientWidth,canScrollY:after.scrollHeight>after.clientHeight,maxScrollX:Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY:Math.max(0,after.scrollHeight-after.clientHeight),isAtLeft:after.x<=1,isAtRight:after.x>=Math.max(0,after.scrollWidth-after.clientWidth)-1,isAtTop:after.y<=1,isAtBottom:after.y>=Math.max(0,after.scrollHeight-after.clientHeight)-1}},params):result=await context.page.evaluate(params2=>{let modeEval=params2.modeEval,selectorEval=params2.selectorEval,dxEval=params2.dxEval,dyEval=params2.dyEval,xEval=params2.xEval,yEval=params2.yEval,behaviorEval=params2.behaviorEval,getTarget=()=>{if(selectorEval){let el=document.querySelector(selectorEval);if(!el)throw new Error(`Element with selector "${selectorEval}" not found`);return el}let scrolling=document.scrollingElement||document.documentElement||document.body;if(!scrolling)throw new Error("No scrolling element available.");return scrolling},readMetrics=el=>({x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight}),clamp=(v,min,max)=>v<min?min:v>max?max:v,doScroll=el=>{let maxX=Math.max(0,el.scrollWidth-el.clientWidth),maxY=Math.max(0,el.scrollHeight-el.clientHeight);if(modeEval==="by"){let nextX=clamp(el.scrollLeft+dxEval,0,maxX),nextY=clamp(el.scrollTop+dyEval,0,maxY);el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="to"){let nextX=typeof xEval=="number"?clamp(xEval,0,maxX):el.scrollLeft,nextY=typeof yEval=="number"?clamp(yEval,0,maxY):el.scrollTop;el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="top"){el.scrollTo({top:0,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="bottom"){el.scrollTo({top:maxY,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="left"){el.scrollTo({left:0,top:el.scrollTop,behavior:behaviorEval});return}if(modeEval==="right"){el.scrollTo({left:maxX,top:el.scrollTop,behavior:behaviorEval});return}},target=getTarget(),before=readMetrics(target);doScroll(target);let after=readMetrics(target),maxScrollX=Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY=Math.max(0,after.scrollHeight-after.clientHeight),canScrollX=after.scrollWidth>after.clientWidth,canScrollY=after.scrollHeight>after.clientHeight,eps=1,isAtLeft=after.x<=eps,isAtRight=after.x>=maxScrollX-eps,isAtTop=after.y<=eps,isAtBottom=after.y>=maxScrollY-eps;return{before,after,canScrollX,canScrollY,maxScrollX,maxScrollY,isAtLeft,isAtRight,isAtTop,isAtBottom}},params),{mode,selector:selector??null,behavior,before:result.before,after:result.after,canScrollX:result.canScrollX,canScrollY:result.canScrollY,maxScrollX:result.maxScrollX,maxScrollY:result.maxScrollY,isAtLeft:result.isAtLeft,isAtRight:result.isAtRight,isAtTop:result.isAtTop,isAtBottom:result.isAtBottom}}};var tools5=[new Click,new Drag,new Fill,new Hover,new PressKey,new ResizeViewport,new ResizeWindow,new Select,new Scroll];import fs7 from"node:fs/promises";import os5 from"node:os";import path6 from"node:path";import{z as z34}from"zod";var DEFAULT_SCREENSHOT_NAME2="screenshot",DEFAULT_TIMEOUT_MS2=0,DEFAULT_WAIT_FOR_TIMEOUT_MS2=3e4,DEFAULT_WAIT_UNTIL="load",DIRECTION_VALUES=["back","forward"],refMapEntrySchema2=z34.object({role:z34.string(),name:z34.string().optional(),selector:z34.string(),nth:z34.number().optional()}),GoBackOrForward=class{name(){return"navigation_go-back-or-forward"}description(){return'\nNavigates to the previous or next page in history.\n- `direction: "back"` \u2014 previous page in history.\n- `direction: "forward"` \u2014 next page in history.\n\nIn case of multiple redirects, the navigation will resolve with the response of the last redirect.\nIf cannot go back/forward, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as <a11y_take-aria-snapshot>.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{direction:z34.enum(DIRECTION_VALUES),timeout:z34.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS2).describe("Max wait ms. 0=no timeout."),waitUntil:z34.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after history navigation."),waitForNavigation:z34.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z34.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS2).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z34.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z34.object({interactiveOnly:z34.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z34.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z34.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z34.object({outputPath:z34.string().optional().default(os5.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z34.string().optional().default(DEFAULT_SCREENSHOT_NAME2).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z34.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z34.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z34.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z34.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z34.string().describe("Contains the URL of the navigated page.").optional(),status:z34.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z34.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z34.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z34.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z34.string().optional(),refs:z34.record(z34.string(),refMapEntrySchema2).optional(),image:z34.object({data:z34.any().describe("Base64-encoded image data."),mimeType:z34.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=args.direction==="back"?await context.page.goBack({timeout:args.timeout,waitUntil:args.waitUntil}):await context.page.goForward({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS2;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os5.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME2;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path6.resolve(outputPath,filename);await fs7.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath,_artifacts:[{type:"IMAGE",filePath:screenshotFilePath,mimeType:screenshotFilePath.endsWith(".jpeg")?"image/jpeg":"image/png"}]},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs8 from"node:fs/promises";import os6 from"node:os";import path7 from"node:path";import{z as z35}from"zod";var DEFAULT_SCREENSHOT_NAME3="screenshot",DEFAULT_TIMEOUT_MS3=0,DEFAULT_WAIT_FOR_TIMEOUT_MS3=3e4,DEFAULT_WAIT_UNTIL2="load",refMapEntrySchema3=z35.object({role:z35.string(),name:z35.string().optional(),selector:z35.string(),nth:z35.number().optional()}),GoTo=class{name(){return"navigation_go-to"}description(){return'\nNavigates to the given URL.\n**NOTE**: The tool either throws an error or returns a main resource response. \nThe only exceptions are navigation to `about:blank` or navigation to the same URL with a different hash, \nwhich would succeed and return empty response.\n\n**By default** (`includeSnapshot: true`), an ARIA snapshot with refs is taken after navigation and returned in `output` and `refs`; you can use refs (e1, e2, ...) in interaction tools without calling <a11y_take-aria-snapshot> separately. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false). Set `includeSnapshot: false` to get only url/status/ok.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{url:z35.string(),timeout:z35.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS3).describe("Max wait ms. 0=no timeout."),waitUntil:z35.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL2).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after navigation."),waitForNavigation:z35.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z35.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS3).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z35.boolean().optional().default(!0).describe("Return ARIA snapshot with refs after nav."),snapshotOptions:z35.object({interactiveOnly:z35.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z35.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z35.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z35.object({outputPath:z35.string().optional().default(os6.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z35.string().optional().default(DEFAULT_SCREENSHOT_NAME3).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z35.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z35.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z35.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z35.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z35.string().describe("Contains the URL of the navigated page.").optional(),status:z35.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z35.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z35.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z35.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z35.string().optional().describe("When includeSnapshot is true: page URL, title, and ARIA tree with refs."),refs:z35.record(z35.string(),refMapEntrySchema3).optional().describe("When includeSnapshot is true: map of ref id (e1, e2, ...) to role/name/selector for use in interaction tools."),image:z35.object({data:z35.any().describe("Base64-encoded image data."),mimeType:z35.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.goto(args.url,{timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS3;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os6.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME3;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path7.resolve(outputPath,filename);await fs8.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath,_artifacts:[{type:"IMAGE",filePath:screenshotFilePath,mimeType:screenshotFilePath.endsWith(".jpeg")?"image/jpeg":"image/png"}]},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs9 from"node:fs/promises";import os7 from"node:os";import path8 from"node:path";import{z as z36}from"zod";var DEFAULT_SCREENSHOT_NAME4="screenshot",DEFAULT_TIMEOUT_MS4=0,DEFAULT_WAIT_FOR_TIMEOUT_MS4=3e4,DEFAULT_WAIT_UNTIL3="load",refMapEntrySchema4=z36.object({role:z36.string(),name:z36.string().optional(),selector:z36.string(),nth:z36.number().optional()}),Reload=class{name(){return"navigation_reload"}description(){return'\nReloads the current page.\nIn case of multiple redirects, the navigation resolves with the response of the last redirect.\nIf the reload does not produce a response, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as <a11y_take-aria-snapshot>.\n\nWhen `includeScreenshot: true`, the screenshot is saved to disk; `screenshotFilePath` is returned. Default path/name: OS temp dir and "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the path.\n '}inputSchema(){return{timeout:z36.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS4).describe("Max wait ms."),waitUntil:z36.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL3).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after reload."),waitForNavigation:z36.boolean().optional().default(!0).describe("Wait for reload then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z36.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS4).describe("Timeout for reload and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z36.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z36.object({interactiveOnly:z36.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z36.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z36.boolean().optional().default(!1).describe("Take a screenshot after reload; saved to disk (default: OS temp dir)."),screenshotOptions:z36.object({outputPath:z36.string().optional().default(os7.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z36.string().optional().default(DEFAULT_SCREENSHOT_NAME4).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z36.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z36.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z36.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z36.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z36.string().describe("Contains the URL of the reloaded page.").optional(),status:z36.number().int().positive().describe("Contains the status code of the reloaded page (e.g., 200 for a success).").optional(),statusText:z36.string().describe('Contains the status text of the reloaded page (e.g. usually an "OK" for a success).').optional(),ok:z36.boolean().describe("Contains a boolean stating whether the reloaded page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z36.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z36.string().optional(),refs:z36.record(z36.string(),refMapEntrySchema4).optional(),image:z36.object({data:z36.any().describe("Base64-encoded image data."),mimeType:z36.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.reload({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS4;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os7.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME4;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path8.resolve(outputPath,filename);await fs9.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath,_artifacts:[{type:"IMAGE",filePath:screenshotFilePath,mimeType:screenshotFilePath.endsWith(".jpeg")?"image/jpeg":"image/png"}]},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};var tools6=[new GoBackOrForward,new GoTo,new Reload];import{z as z37}from"zod";var GetConsoleMessages=class{name(){return"o11y_get-console-messages"}description(){return"Retrieves console messages/logs from the browser with filtering options."}inputSchema(){return{type:z37.enum(getEnumKeyTuples(ConsoleMessageLevelName)).transform(createEnumTransformer(ConsoleMessageLevelName)).optional().describe("Filter by level (this level or higher)."),search:z37.string().optional().describe("Filter by message text."),timestamp:z37.number().int().nonnegative().optional().describe("Only messages at or after this Unix ms."),sequenceNumber:z37.number().int().nonnegative().optional().describe("Incremental: only messages with sequence > this."),limit:z37.object({count:z37.number().int().nonnegative().default(100),from:z37.enum(["start","end"]).default("end").describe("Keep from start or end when truncating.")}).default({count:100,from:"end"}).optional()}}outputSchema(){return{messages:z37.array(z37.object({type:z37.string().describe("Type of the console message."),text:z37.string().describe("Text of the console message."),location:z37.object({url:z37.string().describe("URL of the resource."),lineNumber:z37.number().nonnegative().describe("0-based line number in the resource."),columnNumber:z37.number().nonnegative().describe("0-based column number in the resource.")}).describe("Location of the console message in the resource.").optional(),timestamp:z37.number().int().nonnegative().describe("Unix epoch timestamp (in milliseconds) of the console message."),sequenceNumber:z37.number().int().nonnegative().describe(`
|
|
479
|
+
`.trim()}inputSchema(){return{mode:z33.enum(["by","to","top","bottom","left","right"]).optional().default(DEFAULT_MODE).describe("by=dx,dy; to=x,y; or edge (top/bottom/left/right)."),selector:z33.string().optional().describe("Scrollable container: ref (e.g. from latest <a11y_take-aria-snapshot>), getBy\u2026 expression, or CSS. Omit for viewport."),dx:z33.number().optional(),dy:z33.number().optional(),x:z33.number().optional(),y:z33.number().optional(),behavior:z33.enum(["auto","smooth"]).optional().default(DEFAULT_BEHAVIOR)}}outputSchema(){return{mode:z33.enum(["by","to","top","bottom","left","right"]).describe("The scroll mode used."),selector:z33.string().nullable().describe("The selector of the scroll container if provided; otherwise null (document viewport)."),behavior:z33.enum(["auto","smooth"]).describe("The scroll behavior used."),before:z33.object({x:z33.number().describe("ScrollLeft before scrolling."),y:z33.number().describe("ScrollTop before scrolling."),scrollWidth:z33.number().describe("Total scrollable width before scrolling."),scrollHeight:z33.number().describe("Total scrollable height before scrolling."),clientWidth:z33.number().describe("Viewport/container client width before scrolling."),clientHeight:z33.number().describe("Viewport/container client height before scrolling.")}).describe("Scroll metrics before the scroll action."),after:z33.object({x:z33.number().describe("ScrollLeft after scrolling."),y:z33.number().describe("ScrollTop after scrolling."),scrollWidth:z33.number().describe("Total scrollable width after scrolling."),scrollHeight:z33.number().describe("Total scrollable height after scrolling."),clientWidth:z33.number().describe("Viewport/container client width after scrolling."),clientHeight:z33.number().describe("Viewport/container client height after scrolling.")}).describe("Scroll metrics after the scroll action."),canScrollX:z33.boolean().describe("Whether horizontal scrolling is possible (scrollWidth > clientWidth)."),canScrollY:z33.boolean().describe("Whether vertical scrolling is possible (scrollHeight > clientHeight)."),maxScrollX:z33.number().describe("Maximum horizontal scrollLeft (scrollWidth - clientWidth)."),maxScrollY:z33.number().describe("Maximum vertical scrollTop (scrollHeight - clientHeight)."),isAtLeft:z33.boolean().describe("Whether the scroll position is at the far left."),isAtRight:z33.boolean().describe("Whether the scroll position is at the far right."),isAtTop:z33.boolean().describe("Whether the scroll position is at the very top."),isAtBottom:z33.boolean().describe("Whether the scroll position is at the very bottom.")}}async handle(context,args){let mode=args.mode??DEFAULT_MODE,selector=args.selector,behavior=args.behavior??DEFAULT_BEHAVIOR,dx=args.dx??0,dy=args.dy??0,x=args.x,y=args.y;if(mode==="to"&&typeof x!="number"&&typeof y!="number")throw new Error('mode="to" requires at least one of x or y.');if(mode==="by"&&dx===0&&dy===0)throw new Error('mode="by" requires dx and/or dy to be non-zero.');let params={modeEval:mode,selectorEval:selector,dxEval:dx,dyEval:dy,xEval:x,yEval:y,behaviorEval:behavior},result;return selector?result=await resolveSelectorOrRef(context,selector).evaluate((el,p)=>{let before={x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight},readMetrics=elem2=>({x:elem2.scrollLeft,y:elem2.scrollTop,scrollWidth:elem2.scrollWidth,scrollHeight:elem2.scrollHeight,clientWidth:elem2.clientWidth,clientHeight:elem2.clientHeight}),clamp=(v,min,max)=>Math.max(min,Math.min(max,v)),elem=el,maxX=Math.max(0,elem.scrollWidth-elem.clientWidth),maxY=Math.max(0,elem.scrollHeight-elem.clientHeight);p.modeEval==="by"?elem.scrollTo({left:clamp(elem.scrollLeft+p.dxEval,0,maxX),top:clamp(elem.scrollTop+p.dyEval,0,maxY),behavior:p.behaviorEval}):p.modeEval==="to"?elem.scrollTo({left:typeof p.xEval=="number"?clamp(p.xEval,0,maxX):elem.scrollLeft,top:typeof p.yEval=="number"?clamp(p.yEval,0,maxY):elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="top"?elem.scrollTo({top:0,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="bottom"?elem.scrollTo({top:maxY,left:elem.scrollLeft,behavior:p.behaviorEval}):p.modeEval==="left"?elem.scrollTo({left:0,top:elem.scrollTop,behavior:p.behaviorEval}):p.modeEval==="right"&&elem.scrollTo({left:maxX,top:elem.scrollTop,behavior:p.behaviorEval});let after=readMetrics(elem);return{before,after,canScrollX:after.scrollWidth>after.clientWidth,canScrollY:after.scrollHeight>after.clientHeight,maxScrollX:Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY:Math.max(0,after.scrollHeight-after.clientHeight),isAtLeft:after.x<=1,isAtRight:after.x>=Math.max(0,after.scrollWidth-after.clientWidth)-1,isAtTop:after.y<=1,isAtBottom:after.y>=Math.max(0,after.scrollHeight-after.clientHeight)-1}},params):result=await context.page.evaluate(params2=>{let modeEval=params2.modeEval,selectorEval=params2.selectorEval,dxEval=params2.dxEval,dyEval=params2.dyEval,xEval=params2.xEval,yEval=params2.yEval,behaviorEval=params2.behaviorEval,getTarget=()=>{if(selectorEval){let el=document.querySelector(selectorEval);if(!el)throw new Error(`Element with selector "${selectorEval}" not found`);return el}let scrolling=document.scrollingElement||document.documentElement||document.body;if(!scrolling)throw new Error("No scrolling element available.");return scrolling},readMetrics=el=>({x:el.scrollLeft,y:el.scrollTop,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight,clientWidth:el.clientWidth,clientHeight:el.clientHeight}),clamp=(v,min,max)=>v<min?min:v>max?max:v,doScroll=el=>{let maxX=Math.max(0,el.scrollWidth-el.clientWidth),maxY=Math.max(0,el.scrollHeight-el.clientHeight);if(modeEval==="by"){let nextX=clamp(el.scrollLeft+dxEval,0,maxX),nextY=clamp(el.scrollTop+dyEval,0,maxY);el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="to"){let nextX=typeof xEval=="number"?clamp(xEval,0,maxX):el.scrollLeft,nextY=typeof yEval=="number"?clamp(yEval,0,maxY):el.scrollTop;el.scrollTo({left:nextX,top:nextY,behavior:behaviorEval});return}if(modeEval==="top"){el.scrollTo({top:0,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="bottom"){el.scrollTo({top:maxY,left:el.scrollLeft,behavior:behaviorEval});return}if(modeEval==="left"){el.scrollTo({left:0,top:el.scrollTop,behavior:behaviorEval});return}if(modeEval==="right"){el.scrollTo({left:maxX,top:el.scrollTop,behavior:behaviorEval});return}},target=getTarget(),before=readMetrics(target);doScroll(target);let after=readMetrics(target),maxScrollX=Math.max(0,after.scrollWidth-after.clientWidth),maxScrollY=Math.max(0,after.scrollHeight-after.clientHeight),canScrollX=after.scrollWidth>after.clientWidth,canScrollY=after.scrollHeight>after.clientHeight,eps=1,isAtLeft=after.x<=eps,isAtRight=after.x>=maxScrollX-eps,isAtTop=after.y<=eps,isAtBottom=after.y>=maxScrollY-eps;return{before,after,canScrollX,canScrollY,maxScrollX,maxScrollY,isAtLeft,isAtRight,isAtTop,isAtBottom}},params),{mode,selector:selector??null,behavior,before:result.before,after:result.after,canScrollX:result.canScrollX,canScrollY:result.canScrollY,maxScrollX:result.maxScrollX,maxScrollY:result.maxScrollY,isAtLeft:result.isAtLeft,isAtRight:result.isAtRight,isAtTop:result.isAtTop,isAtBottom:result.isAtBottom}}};var tools5=[new Click,new Drag,new Fill,new Hover,new PressKey,new ResizeViewport,new ResizeWindow,new Select,new Scroll];import fs8 from"node:fs/promises";import os5 from"node:os";import path6 from"node:path";import{z as z34}from"zod";var DEFAULT_SCREENSHOT_NAME2="screenshot",DEFAULT_TIMEOUT_MS2=0,DEFAULT_WAIT_FOR_TIMEOUT_MS2=3e4,DEFAULT_WAIT_UNTIL="load",DIRECTION_VALUES=["back","forward"],refMapEntrySchema2=z34.object({role:z34.string(),name:z34.string().optional(),selector:z34.string(),nth:z34.number().optional()}),GoBackOrForward=class{name(){return"navigation_go-back-or-forward"}description(){return'\nNavigates to the previous or next page in history.\n- `direction: "back"` \u2014 previous page in history.\n- `direction: "forward"` \u2014 next page in history.\n\nIn case of multiple redirects, the navigation will resolve with the response of the last redirect.\nIf cannot go back/forward, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as <a11y_take-aria-snapshot>.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{direction:z34.enum(DIRECTION_VALUES),timeout:z34.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS2).describe("Max wait ms. 0=no timeout."),waitUntil:z34.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after history navigation."),waitForNavigation:z34.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z34.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS2).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z34.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z34.object({interactiveOnly:z34.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z34.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z34.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z34.object({outputPath:z34.string().optional().default(os5.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z34.string().optional().default(DEFAULT_SCREENSHOT_NAME2).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z34.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z34.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z34.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z34.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z34.string().describe("Contains the URL of the navigated page.").optional(),status:z34.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z34.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z34.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z34.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z34.string().optional(),refs:z34.record(z34.string(),refMapEntrySchema2).optional(),image:z34.object({data:z34.any().describe("Base64-encoded image data."),mimeType:z34.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=args.direction==="back"?await context.page.goBack({timeout:args.timeout,waitUntil:args.waitUntil}):await context.page.goForward({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS2;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os5.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME2;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path6.resolve(outputPath,filename);await fs8.mkdir(path6.dirname(filePath),{recursive:!0}),await fs8.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath,_artifacts:[{type:"IMAGE",filePath:screenshotFilePath,mimeType:screenshotFilePath.endsWith(".jpeg")?"image/jpeg":"image/png"}]},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs9 from"node:fs/promises";import os6 from"node:os";import path7 from"node:path";import{z as z35}from"zod";var DEFAULT_SCREENSHOT_NAME3="screenshot",DEFAULT_TIMEOUT_MS3=0,DEFAULT_WAIT_FOR_TIMEOUT_MS3=3e4,DEFAULT_WAIT_UNTIL2="load",refMapEntrySchema3=z35.object({role:z35.string(),name:z35.string().optional(),selector:z35.string(),nth:z35.number().optional()}),GoTo=class{name(){return"navigation_go-to"}description(){return'\nNavigates to the given URL.\n**NOTE**: The tool either throws an error or returns a main resource response. \nThe only exceptions are navigation to `about:blank` or navigation to the same URL with a different hash, \nwhich would succeed and return empty response.\n\n**By default** (`includeSnapshot: true`), an ARIA snapshot with refs is taken after navigation and returned in `output` and `refs`; you can use refs (e1, e2, ...) in interaction tools without calling <a11y_take-aria-snapshot> separately. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false). Set `includeSnapshot: false` to get only url/status/ok.\n\nWhen `includeScreenshot: true`, the screenshot is always saved to disk; `screenshotFilePath` is returned. By default `outputPath` is the OS temp dir and `name` is "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the returned path (e.g. remote, container).\n '}inputSchema(){return{url:z35.string(),timeout:z35.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS3).describe("Max wait ms. 0=no timeout."),waitUntil:z35.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL2).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after navigation."),waitForNavigation:z35.boolean().optional().default(!0).describe("Wait for navigation then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z35.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS3).describe("Timeout for navigation and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z35.boolean().optional().default(!0).describe("Return ARIA snapshot with refs after nav."),snapshotOptions:z35.object({interactiveOnly:z35.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z35.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z35.boolean().optional().default(!1).describe("Take a screenshot after navigation; saved to disk (default: OS temp dir). Use includeBase64 only when file cannot be read from path."),screenshotOptions:z35.object({outputPath:z35.string().optional().default(os6.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z35.string().optional().default(DEFAULT_SCREENSHOT_NAME3).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z35.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z35.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z35.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z35.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z35.string().describe("Contains the URL of the navigated page.").optional(),status:z35.number().int().positive().describe("Contains the status code of the navigated page (e.g., 200 for a success).").optional(),statusText:z35.string().describe('Contains the status text of the navigated page (e.g. usually an "OK" for a success).').optional(),ok:z35.boolean().describe("Contains a boolean stating whether the navigated page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z35.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z35.string().optional().describe("When includeSnapshot is true: page URL, title, and ARIA tree with refs."),refs:z35.record(z35.string(),refMapEntrySchema3).optional().describe("When includeSnapshot is true: map of ref id (e1, e2, ...) to role/name/selector for use in interaction tools."),image:z35.object({data:z35.any().describe("Base64-encoded image data."),mimeType:z35.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.goto(args.url,{timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS3;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os6.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME3;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path7.resolve(outputPath,filename);await fs9.mkdir(path7.dirname(filePath),{recursive:!0}),await fs9.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath,_artifacts:[{type:"IMAGE",filePath:screenshotFilePath,mimeType:screenshotFilePath.endsWith(".jpeg")?"image/jpeg":"image/png"}]},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};import fs10 from"node:fs/promises";import os7 from"node:os";import path8 from"node:path";import{z as z36}from"zod";var DEFAULT_SCREENSHOT_NAME4="screenshot",DEFAULT_TIMEOUT_MS4=0,DEFAULT_WAIT_FOR_TIMEOUT_MS4=3e4,DEFAULT_WAIT_UNTIL3="load",refMapEntrySchema4=z36.object({role:z36.string(),name:z36.string().optional(),selector:z36.string(),nth:z36.number().optional()}),Reload=class{name(){return"navigation_reload"}description(){return'\nReloads the current page.\nIn case of multiple redirects, the navigation resolves with the response of the last redirect.\nIf the reload does not produce a response, returns empty response.\n\nBy default (includeSnapshot: true), an ARIA snapshot with refs is returned. Use `snapshotOptions` for `interactiveOnly` (default false) and `cursorInteractive` (default false), same as <a11y_take-aria-snapshot>.\n\nWhen `includeScreenshot: true`, the screenshot is saved to disk; `screenshotFilePath` is returned. Default path/name: OS temp dir and "screenshot" (same as <content_take-screenshot>). Use `screenshotOptions.includeBase64: true` only when the file cannot be read from the path.\n '}inputSchema(){return{timeout:z36.number().int().nonnegative().optional().default(DEFAULT_TIMEOUT_MS4).describe("Max wait ms."),waitUntil:z36.enum(["load","domcontentloaded","commit"]).optional().default(DEFAULT_WAIT_UNTIL3).describe("Playwright navigation lifecycle: when the main frame reaches this state. Does not use Playwright networkidle; use waitForNavigation for session network-idle after reload."),waitForNavigation:z36.boolean().optional().default(!0).describe("Wait for reload then for network idle before snapshot/screenshot. Default true."),waitForTimeoutMs:z36.number().int().min(0).optional().default(DEFAULT_WAIT_FOR_TIMEOUT_MS4).describe("Timeout for reload and network idle wait (ms). Only when waitForNavigation is true. Default 30000."),includeSnapshot:z36.boolean().optional().default(!0).describe("Return ARIA snapshot with refs."),snapshotOptions:z36.object({interactiveOnly:z36.boolean().optional().default(!1).describe("Only interactive elements get refs. Default false (content roles like headings also included)."),cursorInteractive:z36.boolean().optional().default(!1).describe("Include cursor:pointer / onclick elements. Default false.")}).optional().describe("Options when includeSnapshot is true. Same as <a11y_take-aria-snapshot>."),includeScreenshot:z36.boolean().optional().default(!1).describe("Take a screenshot after reload; saved to disk (default: OS temp dir)."),screenshotOptions:z36.object({outputPath:z36.string().optional().default(os7.tmpdir()).describe("Directory to save the screenshot. Default: OS temp dir."),name:z36.string().optional().default(DEFAULT_SCREENSHOT_NAME4).describe('Base name for the screenshot file. Default: "screenshot".'),fullPage:z36.boolean().optional().default(!0).describe("Capture full scrollable page. Default: true."),type:z36.enum(["png","jpeg"]).optional().default("png").describe("Image format. Default: png."),annotate:z36.boolean().optional().default(!0).describe("Overlay ARIA ref labels on the screenshot. Default: true."),includeBase64:z36.boolean().optional().default(!1).describe("Include base64 image in response; use only when file cannot be read from path (e.g. remote, container).")}).optional().describe('Options when includeScreenshot is true. Same semantics as <content_take-screenshot> (outputPath/name default to tmp and "screenshot").')}}outputSchema(){return{url:z36.string().describe("Contains the URL of the reloaded page.").optional(),status:z36.number().int().positive().describe("Contains the status code of the reloaded page (e.g., 200 for a success).").optional(),statusText:z36.string().describe('Contains the status text of the reloaded page (e.g. usually an "OK" for a success).').optional(),ok:z36.boolean().describe("Contains a boolean stating whether the reloaded page was successful (status in the range 200-299) or not.").optional(),screenshotFilePath:z36.string().optional().describe("When includeScreenshot is true: full path of the saved screenshot file."),output:z36.string().optional(),refs:z36.record(z36.string(),refMapEntrySchema4).optional(),image:z36.object({data:z36.any().describe("Base64-encoded image data."),mimeType:z36.string().describe("MIME type of the image.")}).optional().describe("When includeScreenshot and screenshotOptions.includeBase64 are true: image sent as separate image content part.")}}async handle(context,args){let response=await context.page.reload({timeout:args.timeout,waitUntil:args.waitUntil});if(args.waitForNavigation!==!1){let waitForTimeoutMs=args.waitForTimeoutMs??DEFAULT_WAIT_FOR_TIMEOUT_MS4;await waitForNetworkIdle(context,{timeoutMs:waitForTimeoutMs})}let base={url:response?.url(),status:response?.status(),statusText:response?.statusText(),ok:response?.ok()},output=base;if(args.includeSnapshot!==!1)try{let snapOpts=args.snapshotOptions??{},snapshot=await takeAriaSnapshotWithRefs(context,{interactiveOnly:snapOpts.interactiveOnly??!1,cursorInteractive:snapOpts.cursorInteractive??!1});output={...base,output:snapshot.output,refs:snapshot.refs}}catch{}let screenshotFilePath,image;if(args.includeScreenshot===!0){let opts=args.screenshotOptions??{},outputPath=opts.outputPath??os7.tmpdir(),name=opts.name??DEFAULT_SCREENSHOT_NAME4;try{let screenshotType=(opts.type??"png")==="jpeg"?"jpeg":"png",shot=await captureScreenshot(context,{fullPage:opts.fullPage??!0,type:screenshotType,annotate:opts.annotate??!0}),filename=`${name}-${formattedTimeForFilename()}.${shot.screenshotType}`,filePath=path8.resolve(outputPath,filename);await fs10.mkdir(path8.dirname(filePath),{recursive:!0}),await fs10.writeFile(filePath,shot.rawBuffer),screenshotFilePath=filePath,opts.includeBase64===!0&&(image=shot.image)}catch{}}return{url:output.url,status:output.status,statusText:output.statusText,ok:output.ok,...screenshotFilePath!==void 0&&{screenshotFilePath,_artifacts:[{type:"IMAGE",filePath:screenshotFilePath,mimeType:screenshotFilePath.endsWith(".jpeg")?"image/jpeg":"image/png"}]},...output.output!==void 0&&{output:output.output},...output.refs!==void 0&&{refs:output.refs},...image!==void 0&&{image}}}};var tools6=[new GoBackOrForward,new GoTo,new Reload];import{z as z37}from"zod";var GetConsoleMessages=class{name(){return"o11y_get-console-messages"}description(){return"Retrieves console messages/logs from the browser with filtering options."}inputSchema(){return{type:z37.enum(getEnumKeyTuples(ConsoleMessageLevelName)).transform(createEnumTransformer(ConsoleMessageLevelName)).optional().describe("Filter by level (this level or higher)."),search:z37.string().optional().describe("Filter by message text."),timestamp:z37.number().int().nonnegative().optional().describe("Only messages at or after this Unix ms."),sequenceNumber:z37.number().int().nonnegative().optional().describe("Incremental: only messages with sequence > this."),limit:z37.object({count:z37.number().int().nonnegative().default(100),from:z37.enum(["start","end"]).default("end").describe("Keep from start or end when truncating.")}).default({count:100,from:"end"}).optional()}}outputSchema(){return{messages:z37.array(z37.object({type:z37.string().describe("Type of the console message."),text:z37.string().describe("Text of the console message."),location:z37.object({url:z37.string().describe("URL of the resource."),lineNumber:z37.number().nonnegative().describe("0-based line number in the resource."),columnNumber:z37.number().nonnegative().describe("0-based column number in the resource.")}).describe("Location of the console message in the resource.").optional(),timestamp:z37.number().int().nonnegative().describe("Unix epoch timestamp (in milliseconds) of the console message."),sequenceNumber:z37.number().int().nonnegative().describe(`
|
|
480
480
|
A monotonically increasing sequence number assigned to each console message.
|
|
481
481
|
It reflects the order in which messages were captured and can be used by clients
|
|
482
482
|
to retrieve messages incrementally by requesting only those with a higher sequence
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{augmentToolInputSchema}from"./core-
|
|
1
|
+
import{augmentToolInputSchema}from"./core-G2U4OSL6.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
|
|
package/dist/daemon-server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Execute,init,shutdown,trackToolCalled}from"./core-
|
|
1
|
+
import{Execute,init,shutdown,trackToolCalled}from"./core-WJ3SNBGV.js";import{ScenarioRun,ToolRegistry,augmentToolInputSchema,isToolEnabled,platformInfo}from"./core-G2U4OSL6.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-
|
|
2
|
+
import{Execute,init,shutdown,trackMCPServerStarted,trackToolCalled}from"./core-WJ3SNBGV.js";import{ScenarioRun,ToolRegistry,augmentToolInputSchema,isToolEnabled,platformInfo}from"./core-G2U4OSL6.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),process.on("uncaughtException",err=>{error(`Uncaught exception: ${err.stack??err.message}`)}),process.on("unhandledRejection",reason=>{error(`Unhandled rejection: ${reason instanceof Error?reason.stack??reason.message:String(reason)}`)}),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)});
|