browser-devtools-mcp 0.2.26 → 0.2.27

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/README.md CHANGED
@@ -61,18 +61,18 @@ Choose the platform by running the appropriate MCP server or CLI:
61
61
  ## Browser Platform Features
62
62
 
63
63
  ### Content Tools
64
- - **Screenshots**: Capture full page or specific elements (PNG/JPEG) with image data; optional `annotate: true` overlays numbered labels (1, 2, ...) on elements from the last ARIA snapshot refs and returns an `annotations` array (ref, number, role, name, box). If the ref map is empty, a snapshot is taken automatically. Set `annotateContent: true` to also include content elements (headings, list items, etc.) in the overlay. Set `annotateCursorInteractive: true` to also include cursor-interactive elements (clickable/focusable by CSS without ARIA role) in the overlay. With `selector`, only annotations overlapping that element are returned and box coordinates are element-relative; with `fullPage: true` (no selector), box coordinates are document-relative.
64
+ - **Screenshots**: Capture full page or specific elements (PNG/JPEG) with image data; optional `annotate: true` overlays numbered labels (1, 2, ...) on elements from the last ARIA snapshot refs and returns an `annotations` array (ref, number, role, name, box). If the ref map is empty, a snapshot is taken automatically. Set `annotateContent: true` to also include content elements (headings, list items, etc.) in the overlay. Set `annotateCursorInteractive: true` to also include cursor-interactive elements (clickable/focusable by CSS without ARIA role) in the overlay. The `selector` parameter accepts a ref (e.g. `e1`, `@e1`), a getByRole/getByLabel/getByText/etc. expression, or a CSS selector; with `selector` set, only annotations overlapping that element are returned and box coordinates are element-relative; with `fullPage: true` (no selector), box coordinates are document-relative.
65
65
  - **HTML/Text Extraction**: Get page content with filtering, cleaning, and minification options
66
66
  - **PDF Export**: Save pages as PDF documents with customizable format and margins
67
67
 
68
68
  ### Interaction Tools
69
- - **Click**: Click elements by CSS selector or snapshot ref (e.g. `e1`, `@e1`, `ref=e1`). Refs come from `a11y_take-aria-snapshot` and are valid until the next snapshot or navigation.
70
- - **Fill**: Fill form inputs (selector or ref)
71
- - **Hover**: Hover over elements (selector or ref)
72
- - **Press Key**: Simulate keyboard input; optional selector or ref (e.g. `e1`, `@e1`) to focus an element before sending the key
73
- - **Select**: Select dropdown options (selector or ref)
74
- - **Drag**: Drag and drop operations (source and target as selector or ref)
75
- - **Scroll**: Scroll the page viewport or specific scrollable elements with multiple modes (by, to, top, bottom, left, right); optional selector/ref for scrollable container
69
+ - **Click**: Click elements by snapshot ref (e.g. `e1`, `@e1`, `ref=e1`), Playwright-style expression (e.g. `getByRole('button', { name: 'Login' })`, `getByLabel('Email')`, `getByText('Register')`, `getByPlaceholder('demo@example.com')`, `getByTitle('…')`, `getByAltText('…')`, `getByTestId('…')`), or CSS selector. Refs come from `a11y_take-aria-snapshot` and are valid until the next snapshot or navigation.
70
+ - **Fill**: Fill form inputs (ref, getByRole/getByLabel/getByPlaceholder/etc., or CSS selector)
71
+ - **Hover**: Hover over elements (ref, getByXYZ expression, or CSS selector)
72
+ - **Press Key**: Simulate keyboard input; optional ref, getByXYZ expression, or CSS selector to focus an element before sending the key
73
+ - **Select**: Select dropdown options (ref, getByRole/getByTestId/etc., or CSS selector)
74
+ - **Drag**: Drag and drop (source and target as ref, getByXYZ expression, or CSS selector)
75
+ - **Scroll**: Scroll the page viewport or specific scrollable elements with multiple modes (by, to, top, bottom, left, right); optional ref, getByXYZ, or CSS selector for scrollable container
76
76
  - **Resize Viewport**: Resize the page viewport using Playwright viewport emulation
77
77
  - **Resize Window**: Resize the real browser window (OS-level) using Chrome DevTools Protocol
78
78
 
@@ -97,7 +97,7 @@ Choose the platform by running the appropriate MCP server or CLI:
97
97
  - **Wait for Network Idle**: Wait until the page reaches a network-idle condition based on in-flight request count, useful for SPA pages and before taking screenshots
98
98
 
99
99
  ### Accessibility (A11Y) Tools
100
- - **ARIA Snapshots**: Capture semantic structure and accessibility roles in YAML format. Returns a tree with element refs (e1, e2, ...) and a `refs` map; refs are stored in session context for use in interaction tools (click, fill, hover, select, drag, scroll, press-key) as the selector (e.g. `e1`, `@e1`, `ref=e1`). Refs are valid until the next ARIA snapshot or navigation—re-snapshot after page/DOM changes. Options: **interactiveOnly** (only interactive elements get refs); **cursorInteractive: true** (also assign refs to elements that are clickable/focusable by CSS but have no ARIA role, e.g. custom div/span buttons); **selector** (scope the snapshot to an element).
100
+ - **ARIA Snapshots**: Capture semantic structure and accessibility roles in YAML format. Returns a tree with element refs (e1, e2, ...) and a `refs` map; refs are stored in session context for use in interaction tools (click, fill, hover, select, drag, scroll, press-key) as the selector (e.g. `e1`, `@e1`, `ref=e1`). You can also use Playwright-style expressions in those tools: `getByRole('button', { name: 'Login' })`, `getByLabel('Email')`, `getByText('Register')`, `getByPlaceholder('…')`, `getByTitle('…')`, `getByAltText('…')`, `getByTestId('…')`, or CSS selectors. Refs are valid until the next ARIA snapshot or navigation—re-snapshot after page/DOM changes. Options: **interactiveOnly** (only interactive elements get refs); **cursorInteractive: true** (also assign refs to elements that are clickable/focusable by CSS but have no ARIA role, e.g. custom div/span buttons); **selector** (scope the snapshot to an element).
101
101
  - **AX Tree Snapshots**: Combine Chromium's Accessibility tree with runtime visual diagnostics (bounding boxes, visibility, occlusion detection, computed styles)
102
102
 
103
103
  ### Stub Tools
@@ -1166,7 +1166,7 @@ The server can be configured using environment variables. Configuration is divid
1166
1166
  **Parameters:**
1167
1167
  - `outputPath` (string, optional): Directory path where screenshot will be saved (default: OS temp directory)
1168
1168
  - `name` (string, optional): Screenshot name (default: "screenshot")
1169
- - `selector` (string, optional): CSS selector for element to capture
1169
+ - `selector` (string, optional): Ref (e.g. `e1`, `@e1`), getByRole/getByLabel/getByText/getByPlaceholder/getByTitle/getByAltText/getByTestId expression, or CSS selector for element to capture
1170
1170
  - `fullPage` (boolean, optional): Capture full scrollable page (default: false)
1171
1171
  - `type` (enum, optional): Image format - "png" or "jpeg" (default: "png")
1172
1172
  - `quality` (number, optional): The quality of the image, between 0-100. Not applicable to PNG images, only used for JPEG format (default: 100)
@@ -1237,7 +1237,7 @@ The server can be configured using environment variables. Configuration is divid
1237
1237
  <summary><code>interaction_click</code> - Clicks an element on the page.</summary>
1238
1238
 
1239
1239
  **Parameters:**
1240
- - `selector` (string, required): CSS selector or ref (e.g. `e1`, `@e1`) for the element to click
1240
+ - `selector` (string, required): Ref (e.g. `e1`, `@e1`, `ref=e1`), getByRole/getByLabel/getByText/getByPlaceholder/getByTitle/getByAltText/getByTestId expression, or CSS selector for the element to click
1241
1241
  - `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
1242
1242
  </details>
1243
1243
 
@@ -1245,7 +1245,7 @@ The server can be configured using environment variables. Configuration is divid
1245
1245
  <summary><code>interaction_fill</code> - Fills a form input field.</summary>
1246
1246
 
1247
1247
  **Parameters:**
1248
- - `selector` (string, required): CSS selector or ref (e.g. `e1`, `@e1`) for the input field
1248
+ - `selector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByLabel/getByPlaceholder expression, or CSS selector for the input field
1249
1249
  - `value` (string, required): Value to fill
1250
1250
  - `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
1251
1251
  </details>
@@ -1254,7 +1254,7 @@ The server can be configured using environment variables. Configuration is divid
1254
1254
  <summary><code>interaction_hover</code> - Hovers over an element.</summary>
1255
1255
 
1256
1256
  **Parameters:**
1257
- - `selector` (string, required): CSS selector or ref (e.g. `e1`, `@e1`) for the element to hover
1257
+ - `selector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByText/etc. expression, or CSS selector for the element to hover
1258
1258
  - `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
1259
1259
  </details>
1260
1260
 
@@ -1263,7 +1263,7 @@ The server can be configured using environment variables. Configuration is divid
1263
1263
 
1264
1264
  **Parameters:**
1265
1265
  - `key` (string, required): Key to press (e.g., "Enter", "Escape", "Tab")
1266
- - `selector` (string, optional): CSS selector or ref (e.g. `e1`, `@e1`) to focus before sending the key
1266
+ - `selector` (string, optional): Ref (e.g. `e1`, `@e1`), getByRole/getByLabel/etc. expression, or CSS selector to focus before sending the key
1267
1267
  - `holdMs` (number, optional): Duration in milliseconds to hold the key (repeat duration if `repeat` is true)
1268
1268
  - `repeat` (boolean, optional, default: false): If true, simulates key auto-repeat by pressing repeatedly during `holdMs`
1269
1269
  - `repeatIntervalMs` (number, optional, default: 50, min: 10): Interval between repeated key presses in ms (only when `repeat` is true)
@@ -1274,7 +1274,7 @@ The server can be configured using environment variables. Configuration is divid
1274
1274
  <summary><code>interaction_select</code> - Selects an option from a dropdown.</summary>
1275
1275
 
1276
1276
  **Parameters:**
1277
- - `selector` (string, required): CSS selector or ref (e.g. `e1`, `@e1`) for the select element
1277
+ - `selector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByTestId expression, or CSS selector for the select element
1278
1278
  - `value` (string, required): Value to select
1279
1279
  - `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
1280
1280
  </details>
@@ -1283,8 +1283,8 @@ The server can be configured using environment variables. Configuration is divid
1283
1283
  <summary><code>interaction_drag</code> - Performs drag and drop operation.</summary>
1284
1284
 
1285
1285
  **Parameters:**
1286
- - `sourceSelector` (string, required): CSS selector or ref (e.g. `e1`, `@e1`) for the source element
1287
- - `targetSelector` (string, required): CSS selector or ref for the target element
1286
+ - `sourceSelector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByText/etc. expression, or CSS selector for the source element
1287
+ - `targetSelector` (string, required): Ref, getByRole/getByText/etc. expression, or CSS selector for the target element
1288
1288
  - `timeoutMs` (number, optional): Time to wait for source and target elements in ms (default: 10000)
1289
1289
  </details>
1290
1290
 
@@ -1293,7 +1293,7 @@ The server can be configured using environment variables. Configuration is divid
1293
1293
 
1294
1294
  **Parameters:**
1295
1295
  - `mode` (enum, optional): Scroll mode - "by" (relative delta), "to" (absolute position), "top", "bottom", "left", "right" (default: "by")
1296
- - `selector` (string, optional): CSS selector or ref (e.g. `e1`, `@e1`) for a scrollable container. If omitted, scrolls the document viewport
1296
+ - `selector` (string, optional): Ref (e.g. `e1`, `@e1`), getByRole/getByText/etc. expression, or CSS selector for a scrollable container. If omitted, scrolls the document viewport
1297
1297
  - `dx` (number, optional): Horizontal scroll delta in pixels (used when mode="by", default: 0)
1298
1298
  - `dy` (number, optional): Vertical scroll delta in pixels (used when mode="by", default: 0)
1299
1299
  - `x` (number, optional): Absolute horizontal scroll position in pixels (used when mode="to")
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{platformInfo}from"../core-LM2SLZDP.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-247YVH6X.js";import{Command,Option}from"commander";import{ZodFirstPartyTypeKind}from"zod";function _unwrapZodType(zodType){let current=zodType,isOptional=!1,defaultValue;for(;;){let typeName=current._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodOptional)isOptional=!0,current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodDefault)isOptional=!0,defaultValue=current._def.defaultValue(),current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodNullable)isOptional=!0,current=current._def.innerType;else break}return{innerType:current,isOptional,defaultValue}}function _getDescription(zodType){return zodType._def.description}function _toCamelCase(str){return str.replace(/[-_]([a-z])/g,(_,letter)=>letter.toUpperCase())}function _toKebabCase(str){return str.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function _createOption(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName,option;switch(typeName){case ZodFirstPartyTypeKind.ZodString:option=new Option(`--${flagName} <string>`,description);break;case ZodFirstPartyTypeKind.ZodNumber:option=new Option(`--${flagName} <number>`,description),option.argParser(value=>{let n=Number(value);if(!Number.isFinite(n))throw new Error(`Invalid number: ${value}`);return n});break;case ZodFirstPartyTypeKind.ZodBoolean:option=new Option(`--${flagName}`,description);break;case ZodFirstPartyTypeKind.ZodEnum:let enumValues=innerType._def.values;option=new Option(`--${flagName} <choice>`,description).choices(enumValues);break;case ZodFirstPartyTypeKind.ZodArray:option=new Option(`--${flagName} <value...>`,description);break;case ZodFirstPartyTypeKind.ZodObject:case ZodFirstPartyTypeKind.ZodRecord:option=new Option(`--${flagName} <json>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{throw new Error(`Invalid JSON: ${value}`)}});break;case ZodFirstPartyTypeKind.ZodAny:case ZodFirstPartyTypeKind.ZodUnknown:option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;case ZodFirstPartyTypeKind.ZodLiteral:let literalValue=innerType._def.value;typeof literalValue=="boolean"?option=new Option(`--${flagName}`,description):(option=new Option(`--${flagName} <value>`,description),option.default(literalValue));break;case ZodFirstPartyTypeKind.ZodUnion:let unionOptions=innerType._def.options;if(unionOptions.every(opt=>opt._def.typeName===ZodFirstPartyTypeKind.ZodLiteral)){let choices=unionOptions.map(opt=>String(opt._def.value));option=new Option(`--${flagName} <choice>`,description).choices(choices)}else option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;default:option=new Option(`--${flagName} <value>`,description);break}return defaultValue!==void 0&&option.default(defaultValue),!isOptional&&defaultValue===void 0&&option.makeOptionMandatory(!0),option}function _generateOptionsFromSchema(schema){let options=[];for(let[name,zodType]of Object.entries(schema)){let option=_createOption(name,zodType);option&&options.push(option)}return options}function _parseOptionsToToolInput(options){let result={};for(let[key,value]of Object.entries(options)){let camelKey=_toCamelCase(key);value!==void 0&&(result[camelKey]=value)}return result}function _parseToolName(toolName){let underscoreIndex=toolName.indexOf("_");return underscoreIndex===-1?{domain:"default",commandName:toolName}:{domain:toolName.substring(0,underscoreIndex),commandName:toolName.substring(underscoreIndex+1)}}function registerToolCommands(program,tools2,handler){let domainCommands=new Map;for(let tool of tools2){let{domain,commandName}=_parseToolName(tool.name()),domainCommand=domainCommands.get(domain);domainCommand||(domainCommand=new Command(domain).description(`${domain.charAt(0).toUpperCase()+domain.slice(1)} commands`),domainCommands.set(domain,domainCommand),program.addCommand(domainCommand));let toolCommand=new Command(commandName).description(tool.description().trim()),options=_generateOptionsFromSchema(tool.inputSchema());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{createRequire}from"node:module";import*as path from"node:path";import*as readline from"node:readline";import{fileURLToPath}from"node:url";import{Command as Command2,Option as Option2}from"commander";var require2=createRequire(import.meta.url),__filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename),cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools,DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=path.join(__dirname,"..","daemon-server.js"),env=_buildDaemonEnv(opts);_debug(`Starting daemon server from: ${daemonServerPath}`),_debug(`Daemon port: ${opts.port}`);let child=spawn(process.execPath,[daemonServerPath,"--port",String(opts.port)],{detached:!0,stdio:"ignore",env});child.unref(),_debug(`Daemon process spawned with PID: ${child.pid}`),_output(`Started daemon server as detached process (PID: ${child.pid})`)}async function _ensureDaemonRunning(opts){if(await _isDaemonRunning(opts.port))_debug("Daemon is already running");else{_output(`Daemon server is not running on port ${opts.port}, starting...`),_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;_debug(`Waiting for daemon to be ready (max ${maxRetries} retries, ${retryDelay}ms delay)`);for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),_debug(`Retry ${i+1}/${maxRetries}: checking daemon status...`),await _isDaemonRunning(opts.port)){_debug("Daemon is now ready"),_output("Daemon server is ready");return}throw new Error(`Daemon server failed to start within ${maxRetries*retryDelay/1e3} seconds`)}}async function _stopDaemon(port,timeout){try{return(await fetch(`http://localhost:${port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _callTool(port,toolName,toolInput,sessionId,timeout){let headers={"Content-Type":"application/json"};sessionId&&(headers["session-id"]=sessionId);let request={toolName,toolInput};_debug(`Calling tool: ${toolName}`),_debug("Tool input:",toolInput),_debug(`Session ID: ${sessionId||"(default)"}`),_debug(`Timeout: ${timeout||"none"}`);let startTime=Date.now(),response=await fetch(`http://localhost:${port}/call`,{method:"POST",headers,body:JSON.stringify(request),signal:timeout?AbortSignal.timeout(timeout):void 0}),duration=Date.now()-startTime;if(_debug(`Tool call completed in ${duration}ms, status: ${response.status}`),!response.ok){let errorBody=await response.json().catch(()=>({}));throw _debug("Tool call error:",errorBody),new Error(errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
2
+ import{platformInfo}from"../core-GIHDFCBF.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-247YVH6X.js";import{Command,Option}from"commander";import{ZodFirstPartyTypeKind}from"zod";function _unwrapZodType(zodType){let current=zodType,isOptional=!1,defaultValue;for(;;){let typeName=current._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodOptional)isOptional=!0,current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodDefault)isOptional=!0,defaultValue=current._def.defaultValue(),current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodNullable)isOptional=!0,current=current._def.innerType;else break}return{innerType:current,isOptional,defaultValue}}function _getDescription(zodType){return zodType._def.description}function _toCamelCase(str){return str.replace(/[-_]([a-z])/g,(_,letter)=>letter.toUpperCase())}function _toKebabCase(str){return str.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function _createOption(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName,option;switch(typeName){case ZodFirstPartyTypeKind.ZodString:option=new Option(`--${flagName} <string>`,description);break;case ZodFirstPartyTypeKind.ZodNumber:option=new Option(`--${flagName} <number>`,description),option.argParser(value=>{let n=Number(value);if(!Number.isFinite(n))throw new Error(`Invalid number: ${value}`);return n});break;case ZodFirstPartyTypeKind.ZodBoolean:option=new Option(`--${flagName}`,description);break;case ZodFirstPartyTypeKind.ZodEnum:let enumValues=innerType._def.values;option=new Option(`--${flagName} <choice>`,description).choices(enumValues);break;case ZodFirstPartyTypeKind.ZodArray:option=new Option(`--${flagName} <value...>`,description);break;case ZodFirstPartyTypeKind.ZodObject:case ZodFirstPartyTypeKind.ZodRecord:option=new Option(`--${flagName} <json>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{throw new Error(`Invalid JSON: ${value}`)}});break;case ZodFirstPartyTypeKind.ZodAny:case ZodFirstPartyTypeKind.ZodUnknown:option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;case ZodFirstPartyTypeKind.ZodLiteral:let literalValue=innerType._def.value;typeof literalValue=="boolean"?option=new Option(`--${flagName}`,description):(option=new Option(`--${flagName} <value>`,description),option.default(literalValue));break;case ZodFirstPartyTypeKind.ZodUnion:let unionOptions=innerType._def.options;if(unionOptions.every(opt=>opt._def.typeName===ZodFirstPartyTypeKind.ZodLiteral)){let choices=unionOptions.map(opt=>String(opt._def.value));option=new Option(`--${flagName} <choice>`,description).choices(choices)}else option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;default:option=new Option(`--${flagName} <value>`,description);break}return defaultValue!==void 0&&option.default(defaultValue),!isOptional&&defaultValue===void 0&&option.makeOptionMandatory(!0),option}function _generateOptionsFromSchema(schema){let options=[];for(let[name,zodType]of Object.entries(schema)){let option=_createOption(name,zodType);option&&options.push(option)}return options}function _parseOptionsToToolInput(options){let result={};for(let[key,value]of Object.entries(options)){let camelKey=_toCamelCase(key);value!==void 0&&(result[camelKey]=value)}return result}function _parseToolName(toolName){let underscoreIndex=toolName.indexOf("_");return underscoreIndex===-1?{domain:"default",commandName:toolName}:{domain:toolName.substring(0,underscoreIndex),commandName:toolName.substring(underscoreIndex+1)}}function registerToolCommands(program,tools2,handler){let domainCommands=new Map;for(let tool of tools2){let{domain,commandName}=_parseToolName(tool.name()),domainCommand=domainCommands.get(domain);domainCommand||(domainCommand=new Command(domain).description(`${domain.charAt(0).toUpperCase()+domain.slice(1)} commands`),domainCommands.set(domain,domainCommand),program.addCommand(domainCommand));let toolCommand=new Command(commandName).description(tool.description().trim()),options=_generateOptionsFromSchema(tool.inputSchema());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{createRequire}from"node:module";import*as path from"node:path";import*as readline from"node:readline";import{fileURLToPath}from"node:url";import{Command as Command2,Option as Option2}from"commander";var require2=createRequire(import.meta.url),__filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename),cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools,DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=path.join(__dirname,"..","daemon-server.js"),env=_buildDaemonEnv(opts);_debug(`Starting daemon server from: ${daemonServerPath}`),_debug(`Daemon port: ${opts.port}`);let child=spawn(process.execPath,[daemonServerPath,"--port",String(opts.port)],{detached:!0,stdio:"ignore",env});child.unref(),_debug(`Daemon process spawned with PID: ${child.pid}`),_output(`Started daemon server as detached process (PID: ${child.pid})`)}async function _ensureDaemonRunning(opts){if(await _isDaemonRunning(opts.port))_debug("Daemon is already running");else{_output(`Daemon server is not running on port ${opts.port}, starting...`),_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;_debug(`Waiting for daemon to be ready (max ${maxRetries} retries, ${retryDelay}ms delay)`);for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),_debug(`Retry ${i+1}/${maxRetries}: checking daemon status...`),await _isDaemonRunning(opts.port)){_debug("Daemon is now ready"),_output("Daemon server is ready");return}throw new Error(`Daemon server failed to start within ${maxRetries*retryDelay/1e3} seconds`)}}async function _stopDaemon(port,timeout){try{return(await fetch(`http://localhost:${port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _callTool(port,toolName,toolInput,sessionId,timeout){let headers={"Content-Type":"application/json"};sessionId&&(headers["session-id"]=sessionId);let request={toolName,toolInput};_debug(`Calling tool: ${toolName}`),_debug("Tool input:",toolInput),_debug(`Session ID: ${sessionId||"(default)"}`),_debug(`Timeout: ${timeout||"none"}`);let startTime=Date.now(),response=await fetch(`http://localhost:${port}/call`,{method:"POST",headers,body:JSON.stringify(request),signal:timeout?AbortSignal.timeout(timeout):void 0}),duration=Date.now()-startTime;if(_debug(`Tool call completed in ${duration}ms, status: ${response.status}`),!response.ok){let errorBody=await response.json().catch(()=>({}));throw _debug("Tool call error:",errorBody),new Error(errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
3
3
  `).map(line=>`${prefix}${line}`).join(`
4
4
  `);if(typeof output=="number"||typeof output=="boolean")return`${prefix}${output}`;if(Array.isArray(output))return output.length===0?`${prefix}[]`:output.map(item=>_formatOutput(item,indent)).join(`
5
5
  `);if(typeof output=="object"){let lines=[];for(let[key,value]of Object.entries(output))value!==void 0&&(typeof value=="object"&&value!==null&&!Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):lines.push(`${prefix}${key}: ${value}`));return lines.join(`
@@ -277,11 +277,11 @@ By default, all <script> tags are removed from the output unless "removeScripts"
277
277
  `}inputSchema(){return{selector:z3.string().describe("CSS selector to limit the HTML content to a specific container.").optional(),removeScripts:z3.boolean().describe('Remove all script tags from the HTML (default: "true").').optional().default(!0),removeComments:z3.boolean().describe('Remove all HTML comments (default: "false").').optional().default(!1),removeStyles:z3.boolean().describe('Remove all style tags from the HTML (default: "false").').optional().default(!1),removeMeta:z3.boolean().describe('Remove all meta tags from the HTML (default: "false").').optional().default(!1),cleanHtml:z3.boolean().describe('Perform comprehensive HTML cleaning (default: "false").').optional().default(!1),minify:z3.boolean().describe('Minify the HTML output (default: "false").').optional().default(!1),maxLength:z3.number().int().positive().describe(`Maximum number of characters to return (default: "${DEFAULT_MAX_HTML_LENGTH}").`).optional().default(DEFAULT_MAX_HTML_LENGTH)}}outputSchema(){return{output:z3.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)+`
278
278
  <!-- Output truncated due to size limits -->`),{output}}};import{z as z4}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:z4.string().describe("CSS selector to limit the text content to a specific container.").optional(),maxLength:z4.number().int().positive().describe(`Maximum number of characters to return (default: "${DEFAULT_MAX_TEXT_LENGTH}").`).optional().default(DEFAULT_MAX_TEXT_LENGTH)}}outputSchema(){return{output:z4.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+`
279
279
  `)}return text.trim()},{selector});return output.length>maxLength&&(output=output.slice(0,maxLength)+`
280
- [Output truncated due to size limits]`),{output}}};function getEnumKeyTuples(enumObj){let values=Object.keys(enumObj).filter(key=>isNaN(Number(key))).map(key=>enumObj[key]);if(values.length===0)throw new Error("Enum has no values");return values}function createEnumTransformer(enumObj,opts){let values=Object.keys(enumObj).filter(k=>isNaN(Number(k))).map(k=>enumObj[k]),caseInsensitive=opts?.caseInsensitive??!0,lookup=new Map(values.map(v=>[caseInsensitive?v.toLowerCase():v,v]));return value=>{let key=caseInsensitive?value.toLowerCase():value,found=lookup.get(key);if(found===void 0)throw new Error(`Invalid enum value: "${value}"`);return found}}function formattedTimeForFilename(date=new Date){let pad=n=>String(n).padStart(2,"0");return date.getFullYear()+pad(date.getMonth()+1)+pad(date.getDate())+"-"+pad(date.getHours())+pad(date.getMinutes())+pad(date.getSeconds())}import os from"node:os";import path from"node:path";import{z as z5}from"zod";var SizeUnit=(SizeUnit2=>(SizeUnit2.PIXEL="px",SizeUnit2.INCH="in",SizeUnit2.CENTIMETER="cm",SizeUnit2.MILLIMETER="mm",SizeUnit2))(SizeUnit||{}),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_NAME="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:z5.string().describe("Directory path where PDF will be saved. By default OS tmp directory is used.").optional().default(os.tmpdir()),name:z5.string().describe(`Name of the save/export. Default value is "${DEFAULT_NAME}". Note that final saved/exported file name is in the "{name}-{time}.pdf" format in which "{time}" is in the "YYYYMMDD-HHmmss" format.`).optional().default(DEFAULT_NAME),format:z5.enum(getEnumKeyTuples(PageFormat)).transform(createEnumTransformer(PageFormat)).describe(`Page format. Valid values are: ${getEnumKeyTuples(PageFormat)}.`).optional().default(DEFAULT_FORMAT),printBackground:z5.boolean().describe('Whether to print background graphics (default: "false").').optional().default(!1),margin:z5.object({top:z5.string().describe("Top margin.").default(DEFAULT_MARGIN),right:z5.string().describe("Right margin.").default(DEFAULT_MARGIN),bottom:z5.string().describe("Bottom margin.").default(DEFAULT_MARGIN),left:z5.string().describe("Left margin.").default(DEFAULT_MARGIN)}).describe(`Page margins. Numeric margin values labeled with units ("100px", "10cm", etc ...). Unlabeled values are treated as pixels. Valid units are: ${getEnumKeyTuples(SizeUnit)}.`).optional()}}outputSchema(){return{filePath:z5.string().describe("Full path of the saved PDF file.")}}async handle(context,args){let filename=`${args.name||DEFAULT_NAME}-${formattedTimeForFilename()}.pdf`,filePath=path.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}}};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 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}return context.page.locator(selectorOrRef).first()}import os2 from"node:os";import path2 from"node:path";import jpegjs from"jpeg-js";import{PNG}from"pngjs";import{z as z6}from"zod";var ANNOTATION_OVERLAY_ID="__browser_devtools_mcp_annotations__",ANNOTATE_BOX_TIMEOUT_MS=2e3,ScreenshotType=(ScreenshotType2=>(ScreenshotType2.PNG="png",ScreenshotType2.JPEG="jpeg",ScreenshotType2))(ScreenshotType||{}),DEFAULT_SCREENSHOT_NAME="screenshot",DEFAULT_SCREENSHOT_TYPE="png",DEFAULT_SCREENSHOT_QUALITY=100,TakeScreenshot=class{name(){return"content_take-screenshot"}description(){return`Takes a screenshot of the current page or a specific element.
280
+ [Output truncated due to size limits]`),{output}}};function getEnumKeyTuples(enumObj){let values=Object.keys(enumObj).filter(key=>isNaN(Number(key))).map(key=>enumObj[key]);if(values.length===0)throw new Error("Enum has no values");return values}function createEnumTransformer(enumObj,opts){let values=Object.keys(enumObj).filter(k=>isNaN(Number(k))).map(k=>enumObj[k]),caseInsensitive=opts?.caseInsensitive??!0,lookup=new Map(values.map(v=>[caseInsensitive?v.toLowerCase():v,v]));return value=>{let key=caseInsensitive?value.toLowerCase():value,found=lookup.get(key);if(found===void 0)throw new Error(`Invalid enum value: "${value}"`);return found}}function formattedTimeForFilename(date=new Date){let pad=n=>String(n).padStart(2,"0");return date.getFullYear()+pad(date.getMonth()+1)+pad(date.getDate())+"-"+pad(date.getHours())+pad(date.getMinutes())+pad(date.getSeconds())}import os from"node:os";import path from"node:path";import{z as z5}from"zod";var SizeUnit=(SizeUnit2=>(SizeUnit2.PIXEL="px",SizeUnit2.INCH="in",SizeUnit2.CENTIMETER="cm",SizeUnit2.MILLIMETER="mm",SizeUnit2))(SizeUnit||{}),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_NAME="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:z5.string().describe("Directory path where PDF will be saved. By default OS tmp directory is used.").optional().default(os.tmpdir()),name:z5.string().describe(`Name of the save/export. Default value is "${DEFAULT_NAME}". Note that final saved/exported file name is in the "{name}-{time}.pdf" format in which "{time}" is in the "YYYYMMDD-HHmmss" format.`).optional().default(DEFAULT_NAME),format:z5.enum(getEnumKeyTuples(PageFormat)).transform(createEnumTransformer(PageFormat)).describe(`Page format. Valid values are: ${getEnumKeyTuples(PageFormat)}.`).optional().default(DEFAULT_FORMAT),printBackground:z5.boolean().describe('Whether to print background graphics (default: "false").').optional().default(!1),margin:z5.object({top:z5.string().describe("Top margin.").default(DEFAULT_MARGIN),right:z5.string().describe("Right margin.").default(DEFAULT_MARGIN),bottom:z5.string().describe("Bottom margin.").default(DEFAULT_MARGIN),left:z5.string().describe("Left margin.").default(DEFAULT_MARGIN)}).describe(`Page margins. Numeric margin values labeled with units ("100px", "10cm", etc ...). Unlabeled values are treated as pixels. Valid units are: ${getEnumKeyTuples(SizeUnit)}.`).optional()}}outputSchema(){return{filePath:z5.string().describe("Full path of the saved PDF file.")}}async handle(context,args){let filename=`${args.name||DEFAULT_NAME}-${formattedTimeForFilename()}.pdf`,filePath=path.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}}};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 os2 from"node:os";import path2 from"node:path";import jpegjs from"jpeg-js";import{PNG}from"pngjs";import{z as z6}from"zod";var ANNOTATION_OVERLAY_ID="__browser_devtools_mcp_annotations__",ANNOTATE_BOX_TIMEOUT_MS=2e3,ScreenshotType=(ScreenshotType2=>(ScreenshotType2.PNG="png",ScreenshotType2.JPEG="jpeg",ScreenshotType2))(ScreenshotType||{}),DEFAULT_SCREENSHOT_NAME="screenshot",DEFAULT_SCREENSHOT_TYPE="png",DEFAULT_SCREENSHOT_QUALITY=100,TakeScreenshot=class{name(){return"content_take-screenshot"}description(){return`Takes a screenshot of the current page or a specific element.
281
281
 
282
282
  **Do NOT use this tool to understand page structure.** For structure, use the ARIA snapshot from the last navigation (go-to, go-back, go-forward, reload) or call a11y_take-aria-snapshot / a11y_take-ax-tree-snapshot. Use this tool ONLY when you need to verify a visual problem (e.g. design check, visual bug, contrast, layout) \u2014 not to "see" the page. When in doubt, use snapshots first; screenshot only if snapshots cannot answer the visual question.
283
283
 
284
- The screenshot is always saved to disk; the file path is returned. Do NOT set includeBase64 to true unless the assistant cannot read the file from the returned path (e.g. remote server, container).`}inputSchema(){return{outputPath:z6.string().describe("Directory path where screenshot will be saved. By default OS tmp directory is used.").optional().default(os2.tmpdir()),name:z6.string().describe(`Name of the screenshot. Default value is "${DEFAULT_SCREENSHOT_NAME}". Note that final saved/exported file name is in the "{name}-{time}.{type}" format in which "{time}" is in the "YYYYMMDD-HHmmss" format.`).optional().default(DEFAULT_SCREENSHOT_NAME),selector:z6.string().describe("CSS selector for element to take screenshot.").optional(),fullPage:z6.boolean().describe('Whether to take a screenshot of the full scrollable page, instead of the currently visible viewport (default: "false").').optional().default(!1),type:z6.enum(getEnumKeyTuples(ScreenshotType)).transform(createEnumTransformer(ScreenshotType)).describe(`Page format. Valid values are: ${getEnumKeyTuples(ScreenshotType)}`).optional().default(DEFAULT_SCREENSHOT_TYPE),quality:z6.number().int().min(0).max(DEFAULT_SCREENSHOT_QUALITY).describe("The quality of the image, between 0-100. Not applicable to png images.").optional(),includeBase64:z6.boolean().describe("If true, includes base64 image data in the response (increases payload size). Default is false. The screenshot is always saved to disk; use the returned file path to read it. Set to true ONLY when the assistant cannot access the MCP server file system (e.g. remote/container). Avoid setting to true otherwise.").optional().default(!1),annotate:z6.boolean().optional().default(!1).describe("When true, overlay numbered labels on interactive elements (from last a11y_take-aria-snapshot refs). Labels [1],[2],... map to refs e1,e2,... When selector is also set, only elements overlapping the scoped element are annotated and returned; annotation box coordinates are relative to that element."),annotateContent:z6.boolean().optional().default(!1).describe("When true with annotate, also include content elements (headings, list items, etc.) in the overlay. Uses interactiveOnly: false when building refs for this screenshot."),annotateCursorInteractive:z6.boolean().optional().default(!1).describe("When true with annotate, also include cursor-interactive elements (clickable/focusable by CSS but no ARIA role) in the overlay. Takes a fresh ARIA snapshot with cursorInteractive for this screenshot.")}}outputSchema(){return{filePath:z6.string().describe("Full path of the saved screenshot file."),image:z6.object({data:z6.any().describe("Base64-encoded image data."),mimeType:z6.string().describe("MIME type of the image.")}).optional().describe('Image data included only when "includeBase64" input parameter is set to true.'),annotations:z6.array(z6.object({ref:z6.string(),number:z6.number(),role:z6.string(),name:z6.string().optional(),box:z6.object({x:z6.number(),y:z6.number(),width:z6.number(),height:z6.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.")}}_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}}_scaleImageToFitMessage(buffer,screenshotType){let MAX_PIXELS=12058624e-1,MAX_LINEAR_SIZE=1568,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=this._scaleImageToSize(image,{width,height}),result,currentType=screenshotType,quality=screenshotType==="png"?75:70;screenshotType==="png"?(result=jpegjs.encode(scaledImage,quality).data,currentType="jpeg"):result=jpegjs.encode(scaledImage,quality).data;let iterations=0,MAX_ITERATIONS=5;for(;result.length>819200&&iterations<MAX_ITERATIONS;)quality=Math.max(50,quality-10),quality<=50&&result.length>819200&&(shrink*=.85,width=Math.max(200,image.width*shrink|0),height=Math.max(200,image.height*shrink|0),scaledImage=this._scaleImageToSize(image,{width,height})),result=jpegjs.encode(scaledImage,quality).data,iterations++;return result}async handle(context,args){let screenshotType=args.type||DEFAULT_SCREENSHOT_TYPE,filename=`${args.name||DEFAULT_SCREENSHOT_NAME}-${formattedTimeForFilename()}.${screenshotType}`,filePath=path2.resolve(args.outputPath,filename),quality=screenshotType==="png"?void 0:args.quality??DEFAULT_SCREENSHOT_QUALITY,overlayInjected=!1,annotations,scopedElement=null;if(args.selector&&(scopedElement=await context.page.$(args.selector),!scopedElement))throw new Error(`Element not found: ${args.selector}`);if(args.annotate){let refMap=context.getRefMap(),wantContent=args.annotateContent===!0,wantCursorInteractive=args.annotateCursorInteractive===!0;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)=>{let n=parseInt(k.replace(/^e/,""),10)||0;return n>m?n:m},0);cursorEntries.forEach((entry,i)=>{let ref=`e${maxNum+1+i}`;mergedRefs[ref]={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,args.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}}let options={path:filePath,type:screenshotType,fullPage:!!args.fullPage,quality};scopedElement&&(options.element=scopedElement);try{let screenshot=await context.page.screenshot(options),result={filePath,...annotations&&annotations.length>0?{annotations}:{}};return args.includeBase64&&(result.image={data:this._scaleImageToFitMessage(screenshot,screenshotType),mimeType:`image/${screenshotType}`}),result}finally{overlayInjected&&await context.page.evaluate(id=>{let el=document.getElementById(id);el&&el.remove()},ANNOTATION_OVERLAY_ID).catch(()=>{})}}};var tools2=[new GetAsHtml,new GetAsText,new SaveAsPdf,new TakeScreenshot];import{z as z7}from"zod";var DEFAULT_DEBUG_CONFIG={maxSnapshots:1e3,maxCallStackDepth:20,maxFramesWithScopes:5,maxAsyncStackSegments:10,maxFramesPerAsyncSegment:10,maxDOMMutations:100,maxDOMHtmlSnippetLength:200,maxPendingRequests:1e3,maxResponseBodyLength:1e4,networkCleanupTimeoutMs:5e3},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 _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,domBreakpoints:new Map,networkBreakpoints:new Map,snapshots:[],snapshotSequence:0,config:mergedConfig,enabled:!1,sourceMapsLoaded:!1,exceptionBreakpoint:"none",networkInterceptionEnabled:!1,recentDOMMutations:[]};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",isDOMBreakpoint=event.reason==="DOM",hitBreakpointIds=event.hitBreakpoints||[],hitProbes=[],hitDOMBreakpoint,domChangeInfo;if(isDOMBreakpoint&&event.data){let domData=event.data;for(let domBp of store.domBreakpoints.values())if(domBp.enabled&&(domBp.nodeId===domData.nodeId||!domData.nodeId)){hitDOMBreakpoint=domBp,domChangeInfo={type:domBp.type,selector:domBp.selector,targetNode:domData.targetNode?`<${domData.targetNode.nodeName?.toLowerCase()||"unknown"}>`:void 0,attributeName:domData.attributeName||domBp.attributeName};break}}if(hitDOMBreakpoint&&domChangeInfo)try{let mutationData=await page.evaluate(breakpointId=>{let win=window;if(!win.__domBreakpointMutations)return null;let mutations=win.__domBreakpointMutations;for(let i=mutations.length-1;i>=0;i--)if(mutations[i].breakpointId===breakpointId)return mutations[i];return null},hitDOMBreakpoint.id);mutationData&&(domChangeInfo.oldValue=mutationData.oldValue,domChangeInfo.newValue=mutationData.newValue,domChangeInfo.targetNode=mutationData.targetOuterHTML,mutationData.attributeName&&(domChangeInfo.attributeName=mutationData.attributeName))}catch{}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",shouldCaptureDOMBreakpoint=hitDOMBreakpoint!==void 0;for(let{probe}of hitProbes)probe.hitCount++,probe.lastHitAt=Date.now();if(hitDOMBreakpoint&&(hitDOMBreakpoint.hitCount++,hitDOMBreakpoint.lastHitAt=Date.now()),(shouldCaptureBreakpoint||shouldCaptureException||shouldCaptureDOMBreakpoint)&&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,domChange:domChangeInfo,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),hasExceptionOrDOM=shouldCaptureException||shouldCaptureDOMBreakpoint,snapshotCount=probesToCapture.length+(hasExceptionOrDOM?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 evalResult=await store.v8Api.evaluateOnCallFrame(topFrame.callFrameId,probe.logExpression,{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=hitDOMBreakpoint?.id??"__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(),columnNormalized=options.columnNumber??1,locationKey=_locationKey(options.urlPattern,options.lineNumber,columnNormalized),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=columnNormalized-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?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,probe.columnNumber??1),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}async function setDOMBreakpoint(ctx,page,options){let store=_getStore(ctx);if(!store||!store.enabled)throw new Error("Debugging is not enabled");let cdp=await store.v8Api.getCdp();await cdp.send("DOM.enable");let{root}=await cdp.send("DOM.getDocument",{depth:0}),{nodeId}=await cdp.send("DOM.querySelector",{nodeId:root.nodeId,selector:options.selector});if(!nodeId||nodeId===0)throw new Error(`Element not found: ${options.selector}`);let cdpType=options.type==="subtree-modified"?"subtree-modified":options.type==="attribute-modified"?"attribute-modified":"node-removed";await cdp.send("DOMDebugger.setDOMBreakpoint",{nodeId,type:cdpType});let id=_generateId(),domBreakpoint={id,selector:options.selector,type:options.type,attributeName:options.attributeName,enabled:!0,nodeId,hitCount:0,createdAt:Date.now()};return store.domBreakpoints.set(id,domBreakpoint),await page.evaluate(params=>{let selector=params.selector,breakpointId=params.breakpointId,type=params.type,attrName=params.attrName,maxMutations=params.maxMutations,maxHtmlSnippetLength=params.maxHtmlSnippetLength,element=document.querySelector(selector);if(!element)return;let win=window;win.__domBreakpointData=win.__domBreakpointData||{},win.__domBreakpointMutations=win.__domBreakpointMutations||[],win.__domBreakpointData[breakpointId]={selector,type,attrName,currentAttrs:{}};for(let attr of element.attributes)win.__domBreakpointData[breakpointId].currentAttrs[attr.name]=attr.value;let observer=new MutationObserver(mutations=>{for(let mutation of mutations){let target=mutation.target;if(mutation.type==="attributes"){let attrNameChanged=mutation.attributeName||"";if(attrName&&attrName!==attrNameChanged)continue;let mutationRecord={breakpointId,selector,type:"attribute-modified",attributeName:attrNameChanged,oldValue:mutation.oldValue,newValue:target.getAttribute(attrNameChanged),targetOuterHTML:target.outerHTML.substring(0,maxHtmlSnippetLength),timestamp:Date.now()};win.__domBreakpointMutations.push(mutationRecord),win.__domBreakpointMutations.length>maxMutations&&win.__domBreakpointMutations.shift(),win.__domBreakpointData[breakpointId].currentAttrs[attrNameChanged]=target.getAttribute(attrNameChanged)}else if(mutation.type==="childList"){let mutationRecord={breakpointId,selector,type:"subtree-modified",addedNodes:mutation.addedNodes.length,removedNodes:mutation.removedNodes.length,targetOuterHTML:target.outerHTML.substring(0,maxHtmlSnippetLength),timestamp:Date.now()};win.__domBreakpointMutations.push(mutationRecord),win.__domBreakpointMutations.length>maxMutations&&win.__domBreakpointMutations.shift()}}}),config={attributes:type==="attribute-modified",attributeOldValue:type==="attribute-modified",childList:type==="subtree-modified"||type==="node-removed",subtree:type==="subtree-modified"};attrName&&(config.attributeFilter=[attrName]),observer.observe(element,config),win.__domBreakpointObservers=win.__domBreakpointObservers||{},win.__domBreakpointObservers[breakpointId]=observer},{selector:options.selector,breakpointId:id,type:options.type,attrName:options.attributeName,maxMutations:store.config.maxDOMMutations,maxHtmlSnippetLength:store.config.maxDOMHtmlSnippetLength}),domBreakpoint}async function removeDOMBreakpoint(ctx,domBreakpointId,page){let store=_getStore(ctx);if(!store)return!1;let domBreakpoint=store.domBreakpoints.get(domBreakpointId);if(!domBreakpoint||!domBreakpoint.nodeId)return!1;try{let cdp=await store.v8Api.getCdp(),cdpType=domBreakpoint.type==="subtree-modified"?"subtree-modified":domBreakpoint.type==="attribute-modified"?"attribute-modified":"node-removed";await cdp.send("DOMDebugger.removeDOMBreakpoint",{nodeId:domBreakpoint.nodeId,type:cdpType})}catch{}if(page)try{await page.evaluate(breakpointId=>{let win=window;win.__domBreakpointObservers&&win.__domBreakpointObservers[breakpointId]&&(win.__domBreakpointObservers[breakpointId].disconnect(),delete win.__domBreakpointObservers[breakpointId]),win.__domBreakpointData&&delete win.__domBreakpointData[breakpointId],win.__domBreakpointMutations&&(win.__domBreakpointMutations=win.__domBreakpointMutations.filter(m=>m.breakpointId!==breakpointId))},domBreakpointId)}catch{}return store.domBreakpoints.delete(domBreakpointId)}function listDOMBreakpoints(ctx){let store=_getStore(ctx);return store?Array.from(store.domBreakpoints.values()):[]}async function clearDOMBreakpoints(ctx,page){let store=_getStore(ctx);if(!store)return 0;let ids=Array.from(store.domBreakpoints.keys());for(let id of ids)await removeDOMBreakpoint(ctx,id,page);return ids.length}async function _enableNetworkInterception(store,page){if(store.networkInterceptionEnabled)return;let cdp=await store.v8Api.getCdp();await cdp.send("Fetch.enable",{patterns:[{urlPattern:"*",requestStage:"Request"}]}),cdp.on("Fetch.requestPaused",async event=>{let requestId=event.requestId,requestUrl=event.request.url,method=event.request.method;try{let matchedBreakpoint;for(let bp of store.networkBreakpoints.values()){if(!bp.enabled)continue;let unescapedPattern=bp.urlPattern.replace(/\\([.*+?^${}()|[\]\\/-])/g,"$1");if(new RegExp(unescapedPattern.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,".*")).test(requestUrl)&&!(bp.method&&bp.method.toUpperCase()!==method.toUpperCase())&&bp.timing==="request"){matchedBreakpoint=bp;break}}if(matchedBreakpoint&&matchedBreakpoint.timing==="request"){let requestInfo={url:requestUrl,method,requestHeaders:event.request.headers,requestBody:event.request.postData,resourceType:event.resourceType,timing:"request"},snapshot={id:_generateId(),probeId:matchedBreakpoint.id,timestamp:Date.now(),sequenceNumber:++store.snapshotSequence,url:requestUrl,lineNumber:0,networkRequest:requestInfo,callStack:[],captureTimeMs:0};store.watchExpressions.size>0&&(snapshot.watchResults=await _evaluateWatchExpressions(store,page)),store.snapshots.push(snapshot),store.snapshots.length>store.config.maxSnapshots&&store.snapshots.splice(0,store.snapshots.length-store.config.maxSnapshots),matchedBreakpoint.hitCount++,matchedBreakpoint.lastHitAt=Date.now()}await cdp.send("Fetch.continueRequest",{requestId})}catch{try{await cdp.send("Fetch.continueRequest",{requestId})}catch{}}}),await cdp.send("Network.enable");let pendingRequests=new Map;cdp.on("Network.requestWillBeSent",event=>{if(pendingRequests.set(event.requestId,{method:event.request.method,postData:event.request.postData}),pendingRequests.size>store.config.maxPendingRequests){let firstKey=pendingRequests.keys().next().value;firstKey&&pendingRequests.delete(firstKey)}}),cdp.on("Network.responseReceived",async event=>{let requestId=event.requestId,requestUrl=event.response.url,requestInfo=pendingRequests.get(requestId),method=requestInfo?.method||event.type||"GET",status=event.response.status;for(let bp of store.networkBreakpoints.values()){if(!bp.enabled||bp.timing!=="response")continue;let unescapedPattern=bp.urlPattern.replace(/\\([.*+?^${}()|[\]\\/-])/g,"$1");if(!new RegExp(unescapedPattern.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,".*")).test(requestUrl)||bp.method&&bp.method.toUpperCase()!==method.toUpperCase()||bp.onError&&status<400)continue;let responseBody;try{let bodyResult=await cdp.send("Network.getResponseBody",{requestId});bodyResult.base64Encoded?responseBody=Buffer.from(bodyResult.body,"base64").toString("utf-8"):responseBody=bodyResult.body,responseBody&&responseBody.length>store.config.maxResponseBodyLength&&(responseBody=responseBody.substring(0,store.config.maxResponseBodyLength)+"... [truncated]")}catch{}let networkRequestInfo={url:requestUrl,method,requestBody:requestInfo?.postData,status,statusText:event.response.statusText,responseHeaders:event.response.headers,responseBody,resourceType:event.type,timing:"response"},snapshot={id:_generateId(),probeId:bp.id,timestamp:Date.now(),sequenceNumber:++store.snapshotSequence,url:requestUrl,lineNumber:0,networkRequest:networkRequestInfo,callStack:[],captureTimeMs:0};store.watchExpressions.size>0&&(snapshot.watchResults=await _evaluateWatchExpressions(store,page)),store.snapshots.push(snapshot),store.snapshots.length>store.config.maxSnapshots&&store.snapshots.splice(0,store.snapshots.length-store.config.maxSnapshots),bp.hitCount++,bp.lastHitAt=Date.now(),pendingRequests.delete(requestId);break}}),cdp.on("Network.loadingFinished",event=>{setTimeout(()=>{pendingRequests.delete(event.requestId)},store.config.networkCleanupTimeoutMs)}),store.networkInterceptionEnabled=!0}async function _evaluateWatchExpressions(store,page){let results={};for(let watch of store.watchExpressions.values())try{let value=await page.evaluate(expr=>{try{return(0,eval)(expr)}catch(e){return`[Error: ${e.message}]`}},watch.expression);results[watch.expression]=value}catch(e){results[watch.expression]=`[Error: ${e.message}]`}return results}async function setNetworkBreakpoint(ctx,page,options){let store=_getStore(ctx);if(!store||!store.enabled)throw new Error("Debugging is not enabled");await _enableNetworkInterception(store,page);let id=_generateId(),networkBreakpoint={id,urlPattern:options.urlPattern,method:options.method,timing:options.timing||"request",onError:options.onError,enabled:!0,hitCount:0,createdAt:Date.now()};return store.networkBreakpoints.set(id,networkBreakpoint),networkBreakpoint}function removeNetworkBreakpoint(ctx,networkBreakpointId){let store=_getStore(ctx);return store?store.networkBreakpoints.delete(networkBreakpointId):!1}function listNetworkBreakpoints(ctx){let store=_getStore(ctx);return store?Array.from(store.networkBreakpoints.values()):[]}function clearNetworkBreakpoints(ctx){let store=_getStore(ctx);if(!store)return 0;let count=store.networkBreakpoints.size;return store.networkBreakpoints.clear(),count}var Status=class{name(){return"debug_status"}description(){return`
284
+ The screenshot is always saved to disk; the file path is returned. Do NOT set includeBase64 to true unless the assistant cannot read the file from the returned path (e.g. remote server, container).`}inputSchema(){return{outputPath:z6.string().describe("Directory path where screenshot will be saved. By default OS tmp directory is used.").optional().default(os2.tmpdir()),name:z6.string().describe(`Name of the screenshot. Default value is "${DEFAULT_SCREENSHOT_NAME}". Note that final saved/exported file name is in the "{name}-{time}.{type}" format in which "{time}" is in the "YYYYMMDD-HHmmss" format.`).optional().default(DEFAULT_SCREENSHOT_NAME),selector:z6.string().describe("Selector for element: ref (e1, @e1), getByRole/getByLabel/getByText/etc., or CSS selector.").optional(),fullPage:z6.boolean().describe('Whether to take a screenshot of the full scrollable page, instead of the currently visible viewport (default: "false").').optional().default(!1),type:z6.enum(getEnumKeyTuples(ScreenshotType)).transform(createEnumTransformer(ScreenshotType)).describe(`Page format. Valid values are: ${getEnumKeyTuples(ScreenshotType)}`).optional().default(DEFAULT_SCREENSHOT_TYPE),quality:z6.number().int().min(0).max(DEFAULT_SCREENSHOT_QUALITY).describe("The quality of the image, between 0-100. Not applicable to png images.").optional(),includeBase64:z6.boolean().describe("If true, includes base64 image data in the response (increases payload size). Default is false. The screenshot is always saved to disk; use the returned file path to read it. Set to true ONLY when the assistant cannot access the MCP server file system (e.g. remote/container). Avoid setting to true otherwise.").optional().default(!1),annotate:z6.boolean().optional().default(!1).describe("When true, overlay numbered labels on interactive elements (from last a11y_take-aria-snapshot refs). Labels [1],[2],... map to refs e1,e2,... When selector is also set, only elements overlapping the scoped element are annotated and returned; annotation box coordinates are relative to that element."),annotateContent:z6.boolean().optional().default(!1).describe("When true with annotate, also include content elements (headings, list items, etc.) in the overlay. Uses interactiveOnly: false when building refs for this screenshot."),annotateCursorInteractive:z6.boolean().optional().default(!1).describe("When true with annotate, also include cursor-interactive elements (clickable/focusable by CSS but no ARIA role) in the overlay. Takes a fresh ARIA snapshot with cursorInteractive for this screenshot.")}}outputSchema(){return{filePath:z6.string().describe("Full path of the saved screenshot file."),image:z6.object({data:z6.any().describe("Base64-encoded image data."),mimeType:z6.string().describe("MIME type of the image.")}).optional().describe('Image data included only when "includeBase64" input parameter is set to true.'),annotations:z6.array(z6.object({ref:z6.string(),number:z6.number(),role:z6.string(),name:z6.string().optional(),box:z6.object({x:z6.number(),y:z6.number(),width:z6.number(),height:z6.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.")}}_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}}_scaleImageToFitMessage(buffer,screenshotType){let MAX_PIXELS=12058624e-1,MAX_LINEAR_SIZE=1568,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=this._scaleImageToSize(image,{width,height}),result,currentType=screenshotType,quality=screenshotType==="png"?75:70;screenshotType==="png"?(result=jpegjs.encode(scaledImage,quality).data,currentType="jpeg"):result=jpegjs.encode(scaledImage,quality).data;let iterations=0,MAX_ITERATIONS=5;for(;result.length>819200&&iterations<MAX_ITERATIONS;)quality=Math.max(50,quality-10),quality<=50&&result.length>819200&&(shrink*=.85,width=Math.max(200,image.width*shrink|0),height=Math.max(200,image.height*shrink|0),scaledImage=this._scaleImageToSize(image,{width,height})),result=jpegjs.encode(scaledImage,quality).data,iterations++;return result}async handle(context,args){let screenshotType=args.type||DEFAULT_SCREENSHOT_TYPE,filename=`${args.name||DEFAULT_SCREENSHOT_NAME}-${formattedTimeForFilename()}.${screenshotType}`,filePath=path2.resolve(args.outputPath,filename),quality=screenshotType==="png"?void 0:args.quality??DEFAULT_SCREENSHOT_QUALITY,overlayInjected=!1,annotations,scopedElement=null;if(args.selector&&(scopedElement=await resolveSelectorOrRef(context,args.selector).elementHandle({timeout:1e4}),!scopedElement))throw new Error(`Element not found: ${args.selector}`);if(args.annotate){let refMap=context.getRefMap(),wantContent=args.annotateContent===!0,wantCursorInteractive=args.annotateCursorInteractive===!0;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)=>{let n=parseInt(k.replace(/^e/,""),10)||0;return n>m?n:m},0);cursorEntries.forEach((entry,i)=>{let ref=`e${maxNum+1+i}`;mergedRefs[ref]={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,args.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}}let options={path:filePath,type:screenshotType,fullPage:!!args.fullPage,quality};scopedElement&&(options.element=scopedElement);try{let screenshot=await context.page.screenshot(options),result={filePath,...annotations&&annotations.length>0?{annotations}:{}};return args.includeBase64&&(result.image={data:this._scaleImageToFitMessage(screenshot,screenshotType),mimeType:`image/${screenshotType}`}),result}finally{overlayInjected&&await context.page.evaluate(id=>{let el=document.getElementById(id);el&&el.remove()},ANNOTATION_OVERLAY_ID).catch(()=>{})}}};var tools2=[new GetAsHtml,new GetAsText,new SaveAsPdf,new TakeScreenshot];import{z as z7}from"zod";var DEFAULT_DEBUG_CONFIG={maxSnapshots:1e3,maxCallStackDepth:20,maxFramesWithScopes:5,maxAsyncStackSegments:10,maxFramesPerAsyncSegment:10,maxDOMMutations:100,maxDOMHtmlSnippetLength:200,maxPendingRequests:1e3,maxResponseBodyLength:1e4,networkCleanupTimeoutMs:5e3},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 _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,domBreakpoints:new Map,networkBreakpoints:new Map,snapshots:[],snapshotSequence:0,config:mergedConfig,enabled:!1,sourceMapsLoaded:!1,exceptionBreakpoint:"none",networkInterceptionEnabled:!1,recentDOMMutations:[]};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",isDOMBreakpoint=event.reason==="DOM",hitBreakpointIds=event.hitBreakpoints||[],hitProbes=[],hitDOMBreakpoint,domChangeInfo;if(isDOMBreakpoint&&event.data){let domData=event.data;for(let domBp of store.domBreakpoints.values())if(domBp.enabled&&(domBp.nodeId===domData.nodeId||!domData.nodeId)){hitDOMBreakpoint=domBp,domChangeInfo={type:domBp.type,selector:domBp.selector,targetNode:domData.targetNode?`<${domData.targetNode.nodeName?.toLowerCase()||"unknown"}>`:void 0,attributeName:domData.attributeName||domBp.attributeName};break}}if(hitDOMBreakpoint&&domChangeInfo)try{let mutationData=await page.evaluate(breakpointId=>{let win=window;if(!win.__domBreakpointMutations)return null;let mutations=win.__domBreakpointMutations;for(let i=mutations.length-1;i>=0;i--)if(mutations[i].breakpointId===breakpointId)return mutations[i];return null},hitDOMBreakpoint.id);mutationData&&(domChangeInfo.oldValue=mutationData.oldValue,domChangeInfo.newValue=mutationData.newValue,domChangeInfo.targetNode=mutationData.targetOuterHTML,mutationData.attributeName&&(domChangeInfo.attributeName=mutationData.attributeName))}catch{}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",shouldCaptureDOMBreakpoint=hitDOMBreakpoint!==void 0;for(let{probe}of hitProbes)probe.hitCount++,probe.lastHitAt=Date.now();if(hitDOMBreakpoint&&(hitDOMBreakpoint.hitCount++,hitDOMBreakpoint.lastHitAt=Date.now()),(shouldCaptureBreakpoint||shouldCaptureException||shouldCaptureDOMBreakpoint)&&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,domChange:domChangeInfo,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),hasExceptionOrDOM=shouldCaptureException||shouldCaptureDOMBreakpoint,snapshotCount=probesToCapture.length+(hasExceptionOrDOM?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 evalResult=await store.v8Api.evaluateOnCallFrame(topFrame.callFrameId,probe.logExpression,{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=hitDOMBreakpoint?.id??"__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(),columnNormalized=options.columnNumber??1,locationKey=_locationKey(options.urlPattern,options.lineNumber,columnNormalized),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=columnNormalized-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?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,probe.columnNumber??1),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}async function setDOMBreakpoint(ctx,page,options){let store=_getStore(ctx);if(!store||!store.enabled)throw new Error("Debugging is not enabled");let cdp=await store.v8Api.getCdp();await cdp.send("DOM.enable");let{root}=await cdp.send("DOM.getDocument",{depth:0}),{nodeId}=await cdp.send("DOM.querySelector",{nodeId:root.nodeId,selector:options.selector});if(!nodeId||nodeId===0)throw new Error(`Element not found: ${options.selector}`);let cdpType=options.type==="subtree-modified"?"subtree-modified":options.type==="attribute-modified"?"attribute-modified":"node-removed";await cdp.send("DOMDebugger.setDOMBreakpoint",{nodeId,type:cdpType});let id=_generateId(),domBreakpoint={id,selector:options.selector,type:options.type,attributeName:options.attributeName,enabled:!0,nodeId,hitCount:0,createdAt:Date.now()};return store.domBreakpoints.set(id,domBreakpoint),await page.evaluate(params=>{let selector=params.selector,breakpointId=params.breakpointId,type=params.type,attrName=params.attrName,maxMutations=params.maxMutations,maxHtmlSnippetLength=params.maxHtmlSnippetLength,element=document.querySelector(selector);if(!element)return;let win=window;win.__domBreakpointData=win.__domBreakpointData||{},win.__domBreakpointMutations=win.__domBreakpointMutations||[],win.__domBreakpointData[breakpointId]={selector,type,attrName,currentAttrs:{}};for(let attr of element.attributes)win.__domBreakpointData[breakpointId].currentAttrs[attr.name]=attr.value;let observer=new MutationObserver(mutations=>{for(let mutation of mutations){let target=mutation.target;if(mutation.type==="attributes"){let attrNameChanged=mutation.attributeName||"";if(attrName&&attrName!==attrNameChanged)continue;let mutationRecord={breakpointId,selector,type:"attribute-modified",attributeName:attrNameChanged,oldValue:mutation.oldValue,newValue:target.getAttribute(attrNameChanged),targetOuterHTML:target.outerHTML.substring(0,maxHtmlSnippetLength),timestamp:Date.now()};win.__domBreakpointMutations.push(mutationRecord),win.__domBreakpointMutations.length>maxMutations&&win.__domBreakpointMutations.shift(),win.__domBreakpointData[breakpointId].currentAttrs[attrNameChanged]=target.getAttribute(attrNameChanged)}else if(mutation.type==="childList"){let mutationRecord={breakpointId,selector,type:"subtree-modified",addedNodes:mutation.addedNodes.length,removedNodes:mutation.removedNodes.length,targetOuterHTML:target.outerHTML.substring(0,maxHtmlSnippetLength),timestamp:Date.now()};win.__domBreakpointMutations.push(mutationRecord),win.__domBreakpointMutations.length>maxMutations&&win.__domBreakpointMutations.shift()}}}),config={attributes:type==="attribute-modified",attributeOldValue:type==="attribute-modified",childList:type==="subtree-modified"||type==="node-removed",subtree:type==="subtree-modified"};attrName&&(config.attributeFilter=[attrName]),observer.observe(element,config),win.__domBreakpointObservers=win.__domBreakpointObservers||{},win.__domBreakpointObservers[breakpointId]=observer},{selector:options.selector,breakpointId:id,type:options.type,attrName:options.attributeName,maxMutations:store.config.maxDOMMutations,maxHtmlSnippetLength:store.config.maxDOMHtmlSnippetLength}),domBreakpoint}async function removeDOMBreakpoint(ctx,domBreakpointId,page){let store=_getStore(ctx);if(!store)return!1;let domBreakpoint=store.domBreakpoints.get(domBreakpointId);if(!domBreakpoint||!domBreakpoint.nodeId)return!1;try{let cdp=await store.v8Api.getCdp(),cdpType=domBreakpoint.type==="subtree-modified"?"subtree-modified":domBreakpoint.type==="attribute-modified"?"attribute-modified":"node-removed";await cdp.send("DOMDebugger.removeDOMBreakpoint",{nodeId:domBreakpoint.nodeId,type:cdpType})}catch{}if(page)try{await page.evaluate(breakpointId=>{let win=window;win.__domBreakpointObservers&&win.__domBreakpointObservers[breakpointId]&&(win.__domBreakpointObservers[breakpointId].disconnect(),delete win.__domBreakpointObservers[breakpointId]),win.__domBreakpointData&&delete win.__domBreakpointData[breakpointId],win.__domBreakpointMutations&&(win.__domBreakpointMutations=win.__domBreakpointMutations.filter(m=>m.breakpointId!==breakpointId))},domBreakpointId)}catch{}return store.domBreakpoints.delete(domBreakpointId)}function listDOMBreakpoints(ctx){let store=_getStore(ctx);return store?Array.from(store.domBreakpoints.values()):[]}async function clearDOMBreakpoints(ctx,page){let store=_getStore(ctx);if(!store)return 0;let ids=Array.from(store.domBreakpoints.keys());for(let id of ids)await removeDOMBreakpoint(ctx,id,page);return ids.length}async function _enableNetworkInterception(store,page){if(store.networkInterceptionEnabled)return;let cdp=await store.v8Api.getCdp();await cdp.send("Fetch.enable",{patterns:[{urlPattern:"*",requestStage:"Request"}]}),cdp.on("Fetch.requestPaused",async event=>{let requestId=event.requestId,requestUrl=event.request.url,method=event.request.method;try{let matchedBreakpoint;for(let bp of store.networkBreakpoints.values()){if(!bp.enabled)continue;let unescapedPattern=bp.urlPattern.replace(/\\([.*+?^${}()|[\]\\/-])/g,"$1");if(new RegExp(unescapedPattern.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,".*")).test(requestUrl)&&!(bp.method&&bp.method.toUpperCase()!==method.toUpperCase())&&bp.timing==="request"){matchedBreakpoint=bp;break}}if(matchedBreakpoint&&matchedBreakpoint.timing==="request"){let requestInfo={url:requestUrl,method,requestHeaders:event.request.headers,requestBody:event.request.postData,resourceType:event.resourceType,timing:"request"},snapshot={id:_generateId(),probeId:matchedBreakpoint.id,timestamp:Date.now(),sequenceNumber:++store.snapshotSequence,url:requestUrl,lineNumber:0,networkRequest:requestInfo,callStack:[],captureTimeMs:0};store.watchExpressions.size>0&&(snapshot.watchResults=await _evaluateWatchExpressions(store,page)),store.snapshots.push(snapshot),store.snapshots.length>store.config.maxSnapshots&&store.snapshots.splice(0,store.snapshots.length-store.config.maxSnapshots),matchedBreakpoint.hitCount++,matchedBreakpoint.lastHitAt=Date.now()}await cdp.send("Fetch.continueRequest",{requestId})}catch{try{await cdp.send("Fetch.continueRequest",{requestId})}catch{}}}),await cdp.send("Network.enable");let pendingRequests=new Map;cdp.on("Network.requestWillBeSent",event=>{if(pendingRequests.set(event.requestId,{method:event.request.method,postData:event.request.postData}),pendingRequests.size>store.config.maxPendingRequests){let firstKey=pendingRequests.keys().next().value;firstKey&&pendingRequests.delete(firstKey)}}),cdp.on("Network.responseReceived",async event=>{let requestId=event.requestId,requestUrl=event.response.url,requestInfo=pendingRequests.get(requestId),method=requestInfo?.method||event.type||"GET",status=event.response.status;for(let bp of store.networkBreakpoints.values()){if(!bp.enabled||bp.timing!=="response")continue;let unescapedPattern=bp.urlPattern.replace(/\\([.*+?^${}()|[\]\\/-])/g,"$1");if(!new RegExp(unescapedPattern.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,".*")).test(requestUrl)||bp.method&&bp.method.toUpperCase()!==method.toUpperCase()||bp.onError&&status<400)continue;let responseBody;try{let bodyResult=await cdp.send("Network.getResponseBody",{requestId});bodyResult.base64Encoded?responseBody=Buffer.from(bodyResult.body,"base64").toString("utf-8"):responseBody=bodyResult.body,responseBody&&responseBody.length>store.config.maxResponseBodyLength&&(responseBody=responseBody.substring(0,store.config.maxResponseBodyLength)+"... [truncated]")}catch{}let networkRequestInfo={url:requestUrl,method,requestBody:requestInfo?.postData,status,statusText:event.response.statusText,responseHeaders:event.response.headers,responseBody,resourceType:event.type,timing:"response"},snapshot={id:_generateId(),probeId:bp.id,timestamp:Date.now(),sequenceNumber:++store.snapshotSequence,url:requestUrl,lineNumber:0,networkRequest:networkRequestInfo,callStack:[],captureTimeMs:0};store.watchExpressions.size>0&&(snapshot.watchResults=await _evaluateWatchExpressions(store,page)),store.snapshots.push(snapshot),store.snapshots.length>store.config.maxSnapshots&&store.snapshots.splice(0,store.snapshots.length-store.config.maxSnapshots),bp.hitCount++,bp.lastHitAt=Date.now(),pendingRequests.delete(requestId);break}}),cdp.on("Network.loadingFinished",event=>{setTimeout(()=>{pendingRequests.delete(event.requestId)},store.config.networkCleanupTimeoutMs)}),store.networkInterceptionEnabled=!0}async function _evaluateWatchExpressions(store,page){let results={};for(let watch of store.watchExpressions.values())try{let value=await page.evaluate(expr=>{try{return(0,eval)(expr)}catch(e){return`[Error: ${e.message}]`}},watch.expression);results[watch.expression]=value}catch(e){results[watch.expression]=`[Error: ${e.message}]`}return results}async function setNetworkBreakpoint(ctx,page,options){let store=_getStore(ctx);if(!store||!store.enabled)throw new Error("Debugging is not enabled");await _enableNetworkInterception(store,page);let id=_generateId(),networkBreakpoint={id,urlPattern:options.urlPattern,method:options.method,timing:options.timing||"request",onError:options.onError,enabled:!0,hitCount:0,createdAt:Date.now()};return store.networkBreakpoints.set(id,networkBreakpoint),networkBreakpoint}function removeNetworkBreakpoint(ctx,networkBreakpointId){let store=_getStore(ctx);return store?store.networkBreakpoints.delete(networkBreakpointId):!1}function listNetworkBreakpoints(ctx){let store=_getStore(ctx);return store?Array.from(store.networkBreakpoints.values()):[]}function clearNetworkBreakpoints(ctx){let store=_getStore(ctx);if(!store)return 0;let count=store.networkBreakpoints.size;return store.networkBreakpoints.clear(),count}var Status=class{name(){return"debug_status"}description(){return`
285
285
  Returns the current debugging status including:
286
286
  - Whether debugging is enabled
287
287
  - Source map status
@@ -816,7 +816,7 @@ similar to existing Playwright and Chrome DevTools\u2013based MCP servers, with
816
816
 
817
817
  Take a snapshot first to get real selectors and refs:
818
818
  - Call "a11y_take-aria-snapshot", "a11y_take-ax-tree-snapshot", or "content_get-as-html" (e.g. selector "form") to see actual ids, roles, and markup.
819
- - "a11y_take-aria-snapshot" returns a tree with refs (e1, e2, ...) and stores them in context. Use the selector parameter as a CSS selector OR as a ref: "e1", "@e1", or "ref=e1" in click, fill, hover, select, drag, scroll. Refs are valid until the next snapshot or navigation; re-snapshot after page/DOM changes.
819
+ - "a11y_take-aria-snapshot" returns a tree with refs (e1, e2, ...) and stores them in context. In click, fill, hover, select, drag, scroll use the selector parameter as: a ref ("e1", "@e1", "ref=e1"), a Playwright-style expression (e.g. getByRole('button', { name: 'Login' }), getByLabel('Email'), getByText('Register'), getByPlaceholder('\u2026'), getByTitle('\u2026'), getByAltText('\u2026'), getByTestId('\u2026')), or a CSS selector. Refs are valid until the next snapshot or navigation; re-snapshot after page/DOM changes.
820
820
  - Then use the returned selectors or refs for interaction. Guessing (e.g. input[type="email"]) often fails because real markup may use type="text", wrappers, or different attributes.
821
821
 
822
822
  SNAPSHOT FIRST, THEN INTERACT.
@@ -834,7 +834,7 @@ Core capabilities include:
834
834
 
835
835
  **Browser Control & Interaction:**
836
836
  - Navigation (go to URL, back, forward)
837
- - Element interaction (click, fill, hover, select, drag): selector accepts CSS selector or snapshot ref (e1, @e1, ref=e1); refs come from a11y_take-aria-snapshot and are valid until next snapshot or navigation
837
+ - Element interaction (click, fill, hover, select, drag): selector accepts snapshot ref (e1, @e1, ref=e1), Playwright-style expression (getByRole, getByLabel, getByText, getByPlaceholder, getByTitle, getByAltText, getByTestId), or CSS selector; refs come from a11y_take-aria-snapshot and are valid until next snapshot or navigation
838
838
  - Keyboard simulation (press-key)
839
839
  - Scrolling (viewport or container-based with multiple modes)
840
840
  - Viewport emulation and real window resizing
@@ -954,7 +954,7 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
954
954
  - If you need layout/occlusion/visibility: call "a11y_take-ax-tree-snapshot" (with "checkOcclusion:true" when relevant).
955
955
  - Call "content_take-screenshot" ONLY when you need to verify how something looks visually (e.g. design check, visual bug) or when ARIA/AX are insufficient. Do NOT use screenshot to "see the page" or understand structure.
956
956
 
957
- 3. **Before any element interaction** (click, fill, hover, select, drag): Take a snapshot first to get real selectors and refs. Call "a11y_take-aria-snapshot" (returns refs e1, e2, ... usable as selector "e1" or "@e1"), "a11y_take-ax-tree-snapshot", or "content_get-as-html" (e.g. selector "form") to see actual ids, roles, and markup. Refs are valid until next snapshot or navigation. Do NOT guess selectors from screenshots\u2014real markup often differs (e.g. type="text" instead of type="email"). Snapshot first, then interact.
957
+ 3. **Before any element interaction** (click, fill, hover, select, drag): Take a snapshot first to get real selectors and refs. Call "a11y_take-aria-snapshot" (returns refs e1, e2, ... usable as selector "e1", "@e1", or "ref=e1"), "a11y_take-ax-tree-snapshot", or "content_get-as-html" (e.g. selector "form") to see actual ids, roles, and markup. You can also use Playwright-style expressions as selector: getByRole('button', { name: 'Login' }), getByLabel('Email'), getByText('Register'), getByPlaceholder('\u2026'), getByTitle('\u2026'), getByAltText('\u2026'), getByTestId('\u2026'), or CSS. Refs are valid until next snapshot or navigation. Do NOT guess selectors from screenshots\u2014real markup often differs (e.g. type="text" instead of type="email"). Snapshot first, then interact.
958
958
 
959
959
  4. **Screenshot usage**: When you do take a screenshot, do NOT set "includeBase64" to true unless the assistant cannot read the returned file path (e.g. remote/container). The image is always saved to disk.
960
960
 
@@ -965,7 +965,7 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
965
965
  - Set "includeStyles:true" to analyze computed CSS properties
966
966
 
967
967
  6. **ARIA Snapshot**: Call "a11y_take-aria-snapshot" tool (full page or specific selector)
968
- - Provides semantic structure and accessibility roles; returns refs (e1, e2, ...) for use in interaction tools (selector "e1", "@e1", or "ref=e1")
968
+ - Provides semantic structure and accessibility roles; returns refs (e1, e2, ...) for use in interaction tools (selector "e1", "@e1", "ref=e1", or getByRole/getByLabel/getByText/getByPlaceholder/getByTitle/getByAltText/getByTestId, or CSS)
969
969
  - Set cursorInteractive: true to also get refs for custom clickable elements (e.g. div/span with cursor:pointer or onclick) that have no ARIA role
970
970
  - Best for understanding page hierarchy and accessibility issues
971
971
  - Refs are valid until next snapshot or navigation; re-snapshot after page/DOM changes
@@ -1024,7 +1024,7 @@ When asked to check for UI problems, layout issues, or visual bugs, ALWAYS follo
1024
1024
 
1025
1025
  **Tool Usage Notes:**
1026
1026
  - To understand the page: ARIA snapshot first, then AX tree; do NOT use screenshot for structure\u2014use screenshot only for visual verification when needed.
1027
- - Before any click/fill/hover/select/drag: take a snapshot first to get selectors; do not guess from screenshots.
1027
+ - Before any click/fill/hover/select/drag: take a snapshot first to get selectors or refs; you can use refs (e1, @e1), getByRole/getByLabel/getByText/etc., or CSS; do not guess from screenshots.
1028
1028
  - Screenshot only when you need to verify how the UI looks (e.g. visual bug, design check) or when ARIA/AX are insufficient.
1029
1029
  - Do not set includeBase64 on screenshot unless the assistant cannot read the file path.
1030
1030
  - AX tree: Technical measurements, occlusion, precise positioning, visual diagnostics
@@ -1 +1 @@
1
- import{platformInfo}from"./core-LM2SLZDP.js";import{AVAILABLE_TOOL_DOMAINS,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,debug,enable,error,info,isDebugEnabled}from"./core-247YVH6X.js";import{createRequire}from"node:module";import{Command,Option,InvalidOptionArgumentError}from"commander";import{serve}from"@hono/node-server";import{Hono}from"hono";import{cors}from"hono/cors";import{z}from"zod";var require2=createRequire(import.meta.url),daemonStartTime=0,daemonPort=0,app=new Hono,sessions=new Map,DEFAULT_SESSION_ID="#default",ERRORS={get sessionNotFound(){return _buildErrorResponse(404,"Session Not Found")},get toolNotFound(){return _buildErrorResponse(404,"Tool Not Found")},get internalServerError(){return _buildErrorResponse(500,"Internal Server Error")}};function _buildErrorResponse(code,message){return{error:{code,message}}}async function _closeSession(session){if(session.closed=!0,session.context)try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(session.id)}function _createSession(ctx,sessionId){let now=Date.now(),session={id:sessionId,toolExecutor:platformInfo.toolsInfo.createToolExecutor(()=>sessionId),closed:!1,createdAt:now,lastActiveAt:now};return debug(`Created session with id ${sessionId}`),session}function _getSessionInfo(session){let now=Date.now();return{id:session.id,createdAt:session.createdAt,lastActiveAt:session.lastActiveAt,idleSeconds:Math.floor((now-session.lastActiveAt)/1e3)}}async function _getSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID;return sessions.get(sessionId)}async function _getOrCreateSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID,session=sessions.get(sessionId);return session?debug(`Reusing session with id ${sessionId}`):(debug(`No session could be found with id ${sessionId}`),session=_createSession(ctx,sessionId),sessions.set(sessionId,session)),session}function _scheduleIdleSessionCheck(){let noActiveSession=!1;setInterval(()=>{let currentTime=Date.now();noActiveSession&&sessions.size===0&&(info("No active session found, so terminating daemon server"),process.exit(0));for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>DAEMON_SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),_closeSession(session).then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}));noActiveSession=sessions.size===0},DAEMON_SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.json()}`)}async function startDaemonHTTPServer(port){let allowedDomains=AVAILABLE_TOOL_DOMAINS,toolsToExpose=allowedDomains===void 0?platformInfo.toolsInfo.tools:platformInfo.toolsInfo.tools.filter(tool=>{let domain=tool.name().split("_")[0]?.toLowerCase()??"";return allowedDomains.has(domain)}),toolMap=Object.fromEntries(toolsToExpose.map(tool=>[tool.name(),tool]));app.use("*",cors({origin:"*",allowMethods:["GET","POST","DELETE","OPTIONS"],allowHeaders:["Content-Type","Authorization","session-id"]})),daemonPort=port,daemonStartTime=Date.now();let gracefulShutdown=async signal=>{info(`Received ${signal}, initiating graceful shutdown...`);let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));await Promise.allSettled(closePromises),info("All sessions closed, exiting..."),process.exit(0)};process.on("SIGTERM",()=>gracefulShutdown("SIGTERM")),process.on("SIGINT",()=>gracefulShutdown("SIGINT")),process.on("uncaughtException",err=>{error("Uncaught exception",err)}),process.on("unhandledRejection",reason=>{error("Unhandled rejection",reason)}),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/info",ctx=>{let info2={version:require2("../package.json").version,uptime:Math.floor((Date.now()-daemonStartTime)/1e3),sessionCount:sessions.size,port:daemonPort};return ctx.json(info2)}),app.get("/sessions",ctx=>{let sessionList=[];for(let session of sessions.values())sessionList.push(_getSessionInfo(session));return ctx.json({sessions:sessionList})}),app.get("/session",async ctx=>{let session=await _getSession(ctx);return session?ctx.json(_getSessionInfo(session)):ctx.json(ERRORS.sessionNotFound,404)}),app.post("/shutdown",async ctx=>{info("Shutdown request received, closing all sessions...");let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));return await Promise.allSettled(closePromises),info("All sessions closed, shutting down daemon server..."),setTimeout(()=>{process.exit(0)},500),ctx.json({status:"shutting_down"},200)}),app.post("/call",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let session=await _getOrCreateSession(ctx);session.lastActiveAt=Date.now();let toolCallRequest=await ctx.req.json(),tool=toolMap[toolCallRequest.toolName];if(!tool)return ctx.json(ERRORS.toolNotFound,404);let toolInput;try{toolInput=z.object(tool.inputSchema()).parse(toolCallRequest.toolInput)}catch(err){let errorMessage=err.errors&&Array.isArray(err.errors)?err.errors.map(e=>`${e.path?.join(".")||"input"}: ${e.message}`).join("; "):"Invalid tool input";return ctx.json(_buildErrorResponse(400,`Invalid Tool Request: ${errorMessage}`),400)}try{let toolCallResponse={toolOutput:await session.toolExecutor.executeTool(tool,toolInput)};return ctx.json(toolCallResponse,200)}catch(err){let toolCallResponse={toolError:{code:err.code,message:err.message}};return ctx.json(toolCallResponse,500)}}catch(err){return error("Error occurred while handling tool call request",err),ctx.json(ERRORS.internalServerError,500)}}),app.delete("/session",async ctx=>{try{let session=await _getSession(ctx);return session?(await _closeSession(session),ctx.json({ok:!0},200)):ctx.json(ERRORS.sessionNotFound,404)}catch(err){return error("Error occurred while deleting session",err),ctx.json(ERRORS.internalServerError,500)}}),app.onError((err,ctx)=>(error("Unhandled error in request handler",err),ctx.json({error:{code:500,message:"Internal Server Error"}},500))),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}var isMainModule=import.meta.url===`file://${process.argv[1]}`||import.meta.url===`file://${process.argv[1]}.mjs`||process.argv[1]?.endsWith("daemon-server.js")||process.argv[1]?.endsWith("daemon-server.mjs");if(isMainModule){let parsePort=function(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n};parsePort2=parsePort;let options=new Command().addOption(new Option("--port <number>","port for daemon HTTP server").argParser(parsePort).default(DAEMON_PORT)).allowUnknownOption().parse(process.argv).opts();enable(),info("Starting daemon HTTP server..."),startDaemonHTTPServer(options.port).then(()=>{info("Daemon HTTP server started")}).catch(err=>{error("Failed to start daemon HTTP server",err),process.exit(1)})}var parsePort2;export{startDaemonHTTPServer};
1
+ import{platformInfo}from"./core-GIHDFCBF.js";import{AVAILABLE_TOOL_DOMAINS,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,debug,enable,error,info,isDebugEnabled}from"./core-247YVH6X.js";import{createRequire}from"node:module";import{Command,Option,InvalidOptionArgumentError}from"commander";import{serve}from"@hono/node-server";import{Hono}from"hono";import{cors}from"hono/cors";import{z}from"zod";var require2=createRequire(import.meta.url),daemonStartTime=0,daemonPort=0,app=new Hono,sessions=new Map,DEFAULT_SESSION_ID="#default",ERRORS={get sessionNotFound(){return _buildErrorResponse(404,"Session Not Found")},get toolNotFound(){return _buildErrorResponse(404,"Tool Not Found")},get internalServerError(){return _buildErrorResponse(500,"Internal Server Error")}};function _buildErrorResponse(code,message){return{error:{code,message}}}async function _closeSession(session){if(session.closed=!0,session.context)try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(session.id)}function _createSession(ctx,sessionId){let now=Date.now(),session={id:sessionId,toolExecutor:platformInfo.toolsInfo.createToolExecutor(()=>sessionId),closed:!1,createdAt:now,lastActiveAt:now};return debug(`Created session with id ${sessionId}`),session}function _getSessionInfo(session){let now=Date.now();return{id:session.id,createdAt:session.createdAt,lastActiveAt:session.lastActiveAt,idleSeconds:Math.floor((now-session.lastActiveAt)/1e3)}}async function _getSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID;return sessions.get(sessionId)}async function _getOrCreateSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID,session=sessions.get(sessionId);return session?debug(`Reusing session with id ${sessionId}`):(debug(`No session could be found with id ${sessionId}`),session=_createSession(ctx,sessionId),sessions.set(sessionId,session)),session}function _scheduleIdleSessionCheck(){let noActiveSession=!1;setInterval(()=>{let currentTime=Date.now();noActiveSession&&sessions.size===0&&(info("No active session found, so terminating daemon server"),process.exit(0));for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>DAEMON_SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),_closeSession(session).then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}));noActiveSession=sessions.size===0},DAEMON_SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.json()}`)}async function startDaemonHTTPServer(port){let allowedDomains=AVAILABLE_TOOL_DOMAINS,toolsToExpose=allowedDomains===void 0?platformInfo.toolsInfo.tools:platformInfo.toolsInfo.tools.filter(tool=>{let domain=tool.name().split("_")[0]?.toLowerCase()??"";return allowedDomains.has(domain)}),toolMap=Object.fromEntries(toolsToExpose.map(tool=>[tool.name(),tool]));app.use("*",cors({origin:"*",allowMethods:["GET","POST","DELETE","OPTIONS"],allowHeaders:["Content-Type","Authorization","session-id"]})),daemonPort=port,daemonStartTime=Date.now();let gracefulShutdown=async signal=>{info(`Received ${signal}, initiating graceful shutdown...`);let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));await Promise.allSettled(closePromises),info("All sessions closed, exiting..."),process.exit(0)};process.on("SIGTERM",()=>gracefulShutdown("SIGTERM")),process.on("SIGINT",()=>gracefulShutdown("SIGINT")),process.on("uncaughtException",err=>{error("Uncaught exception",err)}),process.on("unhandledRejection",reason=>{error("Unhandled rejection",reason)}),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/info",ctx=>{let info2={version:require2("../package.json").version,uptime:Math.floor((Date.now()-daemonStartTime)/1e3),sessionCount:sessions.size,port:daemonPort};return ctx.json(info2)}),app.get("/sessions",ctx=>{let sessionList=[];for(let session of sessions.values())sessionList.push(_getSessionInfo(session));return ctx.json({sessions:sessionList})}),app.get("/session",async ctx=>{let session=await _getSession(ctx);return session?ctx.json(_getSessionInfo(session)):ctx.json(ERRORS.sessionNotFound,404)}),app.post("/shutdown",async ctx=>{info("Shutdown request received, closing all sessions...");let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));return await Promise.allSettled(closePromises),info("All sessions closed, shutting down daemon server..."),setTimeout(()=>{process.exit(0)},500),ctx.json({status:"shutting_down"},200)}),app.post("/call",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let session=await _getOrCreateSession(ctx);session.lastActiveAt=Date.now();let toolCallRequest=await ctx.req.json(),tool=toolMap[toolCallRequest.toolName];if(!tool)return ctx.json(ERRORS.toolNotFound,404);let toolInput;try{toolInput=z.object(tool.inputSchema()).parse(toolCallRequest.toolInput)}catch(err){let errorMessage=err.errors&&Array.isArray(err.errors)?err.errors.map(e=>`${e.path?.join(".")||"input"}: ${e.message}`).join("; "):"Invalid tool input";return ctx.json(_buildErrorResponse(400,`Invalid Tool Request: ${errorMessage}`),400)}try{let toolCallResponse={toolOutput:await session.toolExecutor.executeTool(tool,toolInput)};return ctx.json(toolCallResponse,200)}catch(err){let toolCallResponse={toolError:{code:err.code,message:err.message}};return ctx.json(toolCallResponse,500)}}catch(err){return error("Error occurred while handling tool call request",err),ctx.json(ERRORS.internalServerError,500)}}),app.delete("/session",async ctx=>{try{let session=await _getSession(ctx);return session?(await _closeSession(session),ctx.json({ok:!0},200)):ctx.json(ERRORS.sessionNotFound,404)}catch(err){return error("Error occurred while deleting session",err),ctx.json(ERRORS.internalServerError,500)}}),app.onError((err,ctx)=>(error("Unhandled error in request handler",err),ctx.json({error:{code:500,message:"Internal Server Error"}},500))),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}var isMainModule=import.meta.url===`file://${process.argv[1]}`||import.meta.url===`file://${process.argv[1]}.mjs`||process.argv[1]?.endsWith("daemon-server.js")||process.argv[1]?.endsWith("daemon-server.mjs");if(isMainModule){let parsePort=function(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n};parsePort2=parsePort;let options=new Command().addOption(new Option("--port <number>","port for daemon HTTP server").argParser(parsePort).default(DAEMON_PORT)).allowUnknownOption().parse(process.argv).opts();enable(),info("Starting daemon HTTP server..."),startDaemonHTTPServer(options.port).then(()=>{info("Daemon HTTP server started")}).catch(err=>{error("Failed to start daemon HTTP server",err),process.exit(1)})}var parsePort2;export{startDaemonHTTPServer};
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import{platformInfo}from"./core-LM2SLZDP.js";import{AVAILABLE_TOOL_DOMAINS,PORT,SESSION_CLOSE_ON_SOCKET_CLOSE,SESSION_IDLE_CHECK_SECONDS,SESSION_IDLE_SECONDS,TOOL_OUTPUT_SCHEMA_DISABLE,debug,disable,enable,error,info,isDebugEnabled}from"./core-247YVH6X.js";import{createRequire}from"node:module";var require2=createRequire(import.meta.url),SERVER_NAME="browser-devtools-mcp",SERVER_VERSION=require2("../package.json").version;function getServerInstructions(){let parts=[];return parts.push(platformInfo.serverInfo.instructions),parts.join(`
2
+ import{platformInfo}from"./core-GIHDFCBF.js";import{AVAILABLE_TOOL_DOMAINS,PORT,SESSION_CLOSE_ON_SOCKET_CLOSE,SESSION_IDLE_CHECK_SECONDS,SESSION_IDLE_SECONDS,TOOL_OUTPUT_SCHEMA_DISABLE,debug,disable,enable,error,info,isDebugEnabled}from"./core-247YVH6X.js";import{createRequire}from"node:module";var require2=createRequire(import.meta.url),SERVER_NAME="browser-devtools-mcp",SERVER_VERSION=require2("../package.json").version;function getServerInstructions(){let parts=[];return parts.push(platformInfo.serverInfo.instructions),parts.join(`
3
3
 
4
4
  `).trim()}function getServerPolicies(){return platformInfo.serverInfo.policies}import crypto from"node:crypto";import{StreamableHTTPTransport}from"@hono/mcp";import{serve}from"@hono/node-server";import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{Hono}from"hono";import{cors}from"hono/cors";var MCP_TEMPLATE={jsonrpc:"2.0",error:{code:0,message:"N/A"},id:null},MCP_ERRORS={get sessionNotFound(){return _buildMCPErrorResponse(-32001,"Session Not Found")},get unauthorized(){return _buildMCPErrorResponse(-32001,"Unauthorized")},get internalServerError(){return _buildMCPErrorResponse(-32603,"Internal Server Error")}},sessions=new Map;function _buildMCPErrorResponse(code,message){let result={...MCP_TEMPLATE};return result.error.code=code,result.error.message=message,result}function _getImage(response){if("image"in response&&response.image!==null&&typeof response.image=="object"&&"data"in response.image&&"mimeType"in response.image&&Buffer.isBuffer(response.image.data)&&typeof response.image.mimeType=="string"){let image=response.image;return delete response.image,image}}function _toResponse(response){let image=_getImage(response),contents=[];return contents.push({type:"text",text:JSON.stringify(response,null,2)}),image&&(image.mimeType==="image/svg+xml"?contents.push({type:"text",text:image.data.toString(),mimeType:image.mimeType}):contents.push({type:"image",data:image.data.toString("base64"),mimeType:image.mimeType})),{content:contents,structuredContent:response,isError:!1}}function _createServer(opts){let server=new McpServer({name:SERVER_NAME,version:SERVER_VERSION},{capabilities:{resources:{},tools:{}},instructions:getServerInstructions()}),messages=[],policies=getServerPolicies();if(policies)for(let policy of policies)messages.push({role:"user",content:{type:"text",text:policy}});server.registerPrompt("default_system",{title:"Default System Prompt",description:"General behavior for the AI assistant"},async()=>({description:"Defines the assistant's general reasoning and tool usage rules.",messages}));let toolExecutor=platformInfo.toolsInfo.createToolExecutor(()=>opts.sessionIdProvider?opts.sessionIdProvider():""),createToolCallback=tool=>async args=>{try{let response=await toolExecutor.executeTool(tool,args);return _toResponse(response)}catch(error2){return{content:[{type:"text",text:`Error: ${error2.message}`}],isError:!0}}},includeOutputSchema=!TOOL_OUTPUT_SCHEMA_DISABLE,allowedDomains=AVAILABLE_TOOL_DOMAINS;return platformInfo.toolsInfo.tools.forEach(t=>{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:t.description(),inputSchema:t.inputSchema(),outputSchema:t.outputSchema()}:{description:t.description(),inputSchema:t.inputSchema()};server.registerTool(t.name(),toolOptions,createToolCallback(t))}),server}async function _createAndConnectServer(transport,opts){let server=_createServer({config:opts.config,sessionIdProvider:()=>transport.sessionId});return await server.connect(transport),server}function _getConfig(){return{}}function _createSession(ctx,transport,server){let session={transport,server,closed:!1,lastActiveAt:Date.now()},socket=ctx.env.incoming.socket;return socket._mcpRegistered||(socket._mcpRegistered=!0,socket.on("close",async()=>{debug(`Socket, which is for MCP session with id ${transport.sessionId}, has been closed`),SESSION_CLOSE_ON_SOCKET_CLOSE&&await transport.close()})),_registerMCPSessionClose(transport,session.server),debug(`Created MCP server session with id ${transport.sessionId}`),session}async function _createTransport(ctx){let serverConfig=_getConfig(),holder={},transport=new StreamableHTTPTransport({enableJsonResponse:!0,sessionIdGenerator:()=>crypto.randomUUID(),onsessioninitialized:async sessionId=>{let session=_createSession(ctx,transport,holder.server);sessions.set(sessionId,session),debug(`MCP session initialized with id ${sessionId}`)},onsessionclosed:async sessionId=>{debug(`Closing MCP session closed with id ${sessionId} ...`),await transport.close(),debug(`MCP session closed with id ${sessionId}`)}});return holder.server=await _createAndConnectServer(transport,{config:serverConfig}),transport}async function _getTransport(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport}}async function _getOrCreateTransport(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport;debug(`No MCP session could be found with id ${sessionId}`);return}return await _createTransport(ctx)}function _registerMCPSessionClose(transport,mcpServer){let closed=!1;transport.onclose=async()=>{if(debug(`Closing MCP session with id ${transport.sessionId} ...`),closed){debug(`MCP session with id ${transport.sessionId} has already been closed`);return}closed=!0;try{await mcpServer.close(),debug("Closed MCP server")}catch(err){error("Error occurred while closing MCP server",err)}if(transport.sessionId){let session=sessions.get(transport.sessionId);if(session&&(session.closed=!0,session.context))try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(transport.sessionId)}debug(`Closing MCP session with id ${transport.sessionId} ...`)}}function _scheduleIdleSessionCheck(){setInterval(()=>{let currentTime=Date.now();for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),session.transport.close().then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}))},SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.json()}`)}function _markSessionAsActive(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);session&&(session.lastActiveAt=Date.now())}}async function startStdioServer(){let transport=new StdioServerTransport;await _createAndConnectServer(transport,{config:_getConfig()})}var app=new Hono;async function startStreamableHTTPServer(port){app.use("*",cors({origin:"*",allowMethods:["GET","POST","OPTIONS"],allowHeaders:["Content-Type","Authorization","MCP-Protocol-Version"]})),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/ping",ctx=>ctx.json({status:"ok",message:"pong"})),app.get("/mcp",ctx=>ctx.json({status:"ok",protocol:"model-context-protocol",version:"1.0"})),app.post("/mcp",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let transport=await _getOrCreateTransport(ctx);return transport?(_markSessionAsActive(ctx),await transport.handleRequest(ctx)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while handling MCP request",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.delete("/mcp",async ctx=>{try{let transport=await _getTransport(ctx);return transport?(await transport.close(),ctx.json({ok:!0},200)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while deleting MCP session",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}import{Command,Option,InvalidOptionArgumentError}from"commander";function _parsePort(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n}function _getOptions(){return new Command().addOption(new Option("--transport <type>","transport type").choices(["stdio","streamable-http"]).default("stdio")).addOption(new Option("--port <number>","port for Streamable HTTP transport").argParser(_parsePort).default(PORT)).allowUnknownOption().parse(process.argv).opts()}async function main(){let options=_getOptions();options.transport==="stdio"?(disable(),await startStdioServer()):options.transport==="streamable-http"?(info("Starting MCP server..."),await startStreamableHTTPServer(options.port),info("Started MCP Server")):(error(`Invalid transport: ${options.transport}`),process.exit(1))}main().catch(err=>{enable(),error("MCP server error",err),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-devtools-mcp",
3
- "version": "0.2.26",
3
+ "version": "0.2.27",
4
4
  "description": "MCP Server for Browser Dev Tools",
5
5
  "private": false,
6
6
  "type": "module",