browser-devtools-mcp 0.3.1 → 0.3.3
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 +17 -24
- package/dist/cli/runner.js +1 -1
- package/dist/core-24BPAITI.js +5 -0
- package/dist/{core-RRWTV5B5.js → core-BD5K3BG4.js} +19 -34
- package/dist/{core-LAPVCKSF.js → core-GW4S4YVO.js} +1 -1
- package/dist/{core-DOXUXYCD.js → core-PF26XSPV.js} +1 -1
- package/dist/daemon-server.js +1 -1
- package/dist/index.js +2 -2
- package/dist/platform/browser/tools/index.d.ts +2 -1
- package/dist/platform/node/index.d.ts +1 -1
- package/dist/platform/node/tools/index.d.ts +3 -2
- package/dist/platform/types.d.ts +3 -2
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/core-PIEDD6UN.js +0 -5
package/README.md
CHANGED
|
@@ -77,10 +77,9 @@ Choose the platform by running the appropriate MCP server or CLI:
|
|
|
77
77
|
- **Resize Window**: Resize the real browser window (OS-level) using Chrome DevTools Protocol
|
|
78
78
|
|
|
79
79
|
### Navigation Tools
|
|
80
|
-
- **Go To**: Navigate to URLs with configurable wait strategies. By default (`includeSnapshot: true`) returns an ARIA snapshot with refs (`output`, `refs`); set `includeSnapshot: false` for url/status/ok only. Use
|
|
81
|
-
- **Go Back**: Navigate backward in history. Same snapshot/refs behavior as Go To
|
|
82
|
-
- **
|
|
83
|
-
- **Reload**: Reload the current page. Same snapshot/refs behavior as Go To when `includeSnapshot` is true.
|
|
80
|
+
- **Go To**: Navigate to URLs with configurable wait strategies. By default (`includeSnapshot: true`) returns an ARIA snapshot with refs (`output`, `refs`); set `includeSnapshot: false` for url/status/ok only. Use **snapshotOptions** (e.g. `interactiveOnly`, `cursorInteractive`) to control which elements get refs (same as `a11y_take-aria-snapshot`). Optional **includeScreenshot** saves a screenshot to disk and returns `screenshotFilePath`; use **screenshotOptions** (outputPath, name, fullPage, type, annotate, includeBase64) — defaults: OS temp dir, name "screenshot"; set `includeBase64: true` only when the file cannot be read from the path (e.g. remote, container).
|
|
81
|
+
- **Go Back / Go Forward**: Navigate backward or forward in history. Same snapshot/refs and optional screenshot behavior as Go To (snapshotOptions, includeScreenshot, screenshotOptions).
|
|
82
|
+
- **Reload**: Reload the current page. Same snapshot/refs and optional screenshot behavior as Go To (snapshotOptions, includeScreenshot, screenshotOptions).
|
|
84
83
|
|
|
85
84
|
### Run Tools
|
|
86
85
|
- **JS in Browser**: Execute JavaScript code inside the active browser page (page context with access to window, document, DOM, and Web APIs)
|
|
@@ -591,7 +590,7 @@ node-devtools-cli
|
|
|
591
590
|
|
|
592
591
|
### Browser CLI Commands
|
|
593
592
|
|
|
594
|
-
`browser-devtools-cli` organizes tools into domain-based subcommands:
|
|
593
|
+
`browser-devtools-cli` organizes tools into domain-based subcommands. **Object parameters** (e.g. `screenshotOptions`, `snapshotOptions`) must be passed as a JSON string: `--screenshot-options '{"outputPath":"/tmp","name":"myshot"}'` or `--snapshot-options '{"interactiveOnly":false}'`. Run `browser-devtools-cli navigation go-to --help` (or the relevant subcommand) to see all options.
|
|
595
594
|
|
|
596
595
|
```
|
|
597
596
|
browser-devtools-cli
|
|
@@ -1163,7 +1162,6 @@ Only non-personal, non-sensitive data is sent. No page content, URLs, error mess
|
|
|
1163
1162
|
- Any personally identifiable information (PII)
|
|
1164
1163
|
- `error.message` content — only the error class name and code are sent
|
|
1165
1164
|
- URLs, page content, screenshots, or any application data
|
|
1166
|
-
- IP addresses — PostHog is configured to discard them
|
|
1167
1165
|
|
|
1168
1166
|
A persistent anonymous UUID is stored locally at `~/.browser-devtools-mcp/config.json`. It is never linked to any user identity.
|
|
1169
1167
|
|
|
@@ -1431,24 +1429,24 @@ Once disabled, no data is sent and no network requests are made to PostHog.
|
|
|
1431
1429
|
- `timeout` (number, optional): Maximum operation time in milliseconds (default: 0 - no timeout)
|
|
1432
1430
|
- `waitUntil` (enum, optional): When to consider navigation succeeded - "load", "domcontentloaded", "networkidle", or "commit" (default: "load")
|
|
1433
1431
|
- `includeSnapshot` (boolean, optional): When true (default), take an ARIA snapshot with refs after navigation and include `output` and `refs` in the response; when false, only url/status/ok are returned.
|
|
1434
|
-
- `
|
|
1435
|
-
- `
|
|
1432
|
+
- `snapshotOptions` (object, optional): When includeSnapshot is true, options for the snapshot. **interactiveOnly** (boolean, default false): only interactive elements get refs; **cursorInteractive** (boolean, default false): include cursor-interactive elements (same as a11y_take-aria-snapshot).
|
|
1433
|
+
- `includeScreenshot` (boolean, optional): When true, take a screenshot after navigation; saved to disk, path returned in `screenshotFilePath`. Default false.
|
|
1434
|
+
- `screenshotOptions` (object, optional): When includeScreenshot is true. **outputPath** (string, default: OS temp dir), **name** (string, default: "screenshot"), **fullPage** (boolean, default true), **type** ("png" | "jpeg", default "png"), **annotate** (boolean, default true), **includeBase64** (boolean, default false): include image in response as separate MCP content part — use only when file cannot be read from path (e.g. remote, container).
|
|
1436
1435
|
|
|
1437
|
-
**Returns:**
|
|
1438
|
-
- `url`
|
|
1439
|
-
- `
|
|
1440
|
-
- `statusText` (string): HTTP status text
|
|
1441
|
-
- `ok` (boolean): Whether navigation was successful (2xx status)
|
|
1436
|
+
**Returns:** (order: url, status, statusText, ok, screenshotFilePath, output, refs, image)
|
|
1437
|
+
- `url`, `status`, `statusText`, `ok`: Navigation result.
|
|
1438
|
+
- `screenshotFilePath` (string, optional): When includeScreenshot is true, full path of the saved screenshot file.
|
|
1442
1439
|
- `output` (string, optional): When includeSnapshot is true, ARIA snapshot text (page URL, title, YAML tree).
|
|
1443
1440
|
- `refs` (record, optional): When includeSnapshot is true, map of ref id (e1, e2, ...) to role/name/selector; use in interaction tools (e.g. click @e1).
|
|
1441
|
+
- `image` (object, optional): When includeScreenshot and screenshotOptions.includeBase64 are true, image data (data, mimeType) sent as separate image content part by MCP.
|
|
1444
1442
|
</details>
|
|
1445
1443
|
|
|
1446
1444
|
<details>
|
|
1447
1445
|
<summary><code>navigation_go-back-or-forward</code> - Navigates back or forward in browser history.</summary>
|
|
1448
1446
|
|
|
1449
|
-
**Parameters:** `direction` (required: `"back"` or `"forward"`), `timeout`, `waitUntil`, `includeSnapshot` (default true), `
|
|
1447
|
+
**Parameters:** `direction` (required: `"back"` or `"forward"`), `timeout`, `waitUntil`, `includeSnapshot` (default true), `snapshotOptions` (object: interactiveOnly, cursorInteractive), `includeScreenshot` (boolean, default false), `screenshotOptions` (object: outputPath, name, fullPage, type, annotate, includeBase64) — same semantics as navigation_go-to.
|
|
1450
1448
|
|
|
1451
|
-
**Returns:**
|
|
1449
|
+
**Returns:** Same shape as navigation_go-to (url, status, statusText, ok, screenshotFilePath, output, refs, image).
|
|
1452
1450
|
</details>
|
|
1453
1451
|
|
|
1454
1452
|
<details>
|
|
@@ -1458,16 +1456,11 @@ Once disabled, no data is sent and no network requests are made to PostHog.
|
|
|
1458
1456
|
- `timeout` (number, optional): Maximum operation time in milliseconds (default: 0 - no timeout)
|
|
1459
1457
|
- `waitUntil` (enum, optional): When to consider navigation succeeded - "load", "domcontentloaded", "networkidle", or "commit" (default: "load")
|
|
1460
1458
|
- `includeSnapshot` (boolean, optional): When true (default), take an ARIA snapshot with refs after reload and include `output` and `refs`; when false, only url/status/ok.
|
|
1461
|
-
- `
|
|
1462
|
-
- `
|
|
1459
|
+
- `snapshotOptions` (object, optional): When includeSnapshot is true. **interactiveOnly** (boolean, default false), **cursorInteractive** (boolean, default false) — same as a11y_take-aria-snapshot.
|
|
1460
|
+
- `includeScreenshot` (boolean, optional): When true, take a screenshot after reload; saved to disk. Default false.
|
|
1461
|
+
- `screenshotOptions` (object, optional): When includeScreenshot is true; same shape as navigation_go-to (outputPath, name, fullPage, type, annotate, includeBase64).
|
|
1463
1462
|
|
|
1464
|
-
**Returns:**
|
|
1465
|
-
- `url` (string): Final URL after reload
|
|
1466
|
-
- `status` (number): HTTP status code
|
|
1467
|
-
- `statusText` (string): HTTP status text
|
|
1468
|
-
- `ok` (boolean): Whether reload was successful (2xx status)
|
|
1469
|
-
- `output` (string, optional): When includeSnapshot is true, ARIA snapshot text.
|
|
1470
|
-
- `refs` (record, optional): When includeSnapshot is true, map of ref id to role/name/selector for use in interaction tools.
|
|
1463
|
+
**Returns:** Same shape as navigation_go-to (url, status, statusText, ok, screenshotFilePath, output, refs, image).
|
|
1471
1464
|
</details>
|
|
1472
1465
|
|
|
1473
1466
|
### Run Tools
|
package/dist/cli/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{isToolEnabled,platformInfo}from"../core-RRWTV5B5.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-DOXUXYCD.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,toolName){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(),tool.name());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{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.filter(isToolEnabled),DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=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(()=>({}));_debug("Tool call error:",errorBody);let message=errorBody?.toolError?.message||errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`;throw new Error(message)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
|
|
2
|
+
import{isToolEnabled,platformInfo}from"../core-BD5K3BG4.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-PF26XSPV.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,toolName){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(),tool.name());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{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.filter(isToolEnabled),DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=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(()=>({}));_debug("Tool call error:",errorBody);let message=errorBody?.toolError?.message||errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`;throw new Error(message)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
|
|
3
3
|
`).map(line=>`${prefix}${line}`).join(`
|
|
4
4
|
`);if(typeof output=="number"||typeof output=="boolean")return`${prefix}${output}`;if(Array.isArray(output))return output.length===0?`${prefix}[]`:output.map(item=>_formatOutput(item,indent)).join(`
|
|
5
5
|
`);if(typeof output=="object"){let lines=[];for(let[key,value]of Object.entries(output))value!==void 0&&(typeof value=="object"&&value!==null&&!Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):lines.push(`${prefix}${key}: ${value}`));return lines.join(`
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import*as fs2 from"node:fs";import{fileURLToPath}from"node:url";import{PostHog}from"posthog-node";import*as crypto from"node:crypto";import*as fs from"node:fs";import*as os from"node:os";import*as path from"node:path";var CONFIG_DIR=path.join(os.homedir(),".browser-devtools-mcp"),CONFIG_FILE=path.join(CONFIG_DIR,"config.json");function readConfig(){let existing={};if(fs.existsSync(CONFIG_FILE))try{let raw=fs.readFileSync(CONFIG_FILE,"utf-8");existing=JSON.parse(raw)}catch{}let dirty=!1;return existing.anonymousId||(existing.anonymousId=crypto.randomUUID(),dirty=!0),existing.telemetryEnabled===void 0&&(existing.telemetryEnabled=!0,dirty=!0),existing.telemetryNoticeShown===void 0&&(existing.telemetryNoticeShown=!1,dirty=!0),dirty&&_writeConfig(existing),existing}function updateConfig(updates){let current=readConfig();_writeConfig({...current,...updates})}function _writeConfig(config){try{fs.existsSync(CONFIG_DIR)||fs.mkdirSync(CONFIG_DIR,{recursive:!0}),fs.writeFileSync(CONFIG_FILE,JSON.stringify(config,null,2),"utf-8")}catch{}}var POSTHOG_API_KEY=process.env.POSTHOG_API_KEY??"phc_ekFEnQ9ipk0F1BbO0KCkaD8OaYPa4bIqqUoxsCfeFsy",POSTHOG_HOST="https://us.posthog.com",_initialized=!1,_client=null,_anonymousId="",_enabled=!1,_source="mcp-unknown";var _browserDevtoolsVersion=(()=>{let candidates=[new URL("../package.json",import.meta.url),new URL("../../package.json",import.meta.url)];for(let candidate of candidates)try{let raw=fs2.readFileSync(fileURLToPath(candidate),"utf-8"),ver=JSON.parse(raw).version;if(ver)return ver}catch{}return"unknown"})();function _detectMcpClientSource(clientInfo){if(process.env.CURSOR_TRACE_ID)return"mcp-cursor";if(process.env.CLAUDE_DESKTOP)return"mcp-claude";if(clientInfo?.name){let name=clientInfo.name.toLowerCase();if(name.includes("cursor"))return"mcp-cursor";if(name.includes("claude")||name.includes("local-agent-mode"))return"mcp-claude";if(name.includes("codex")||name.includes("openai"))return"mcp-codex"}return"mcp-unknown"}function _detectCLISource(){return"cli"}function _buildBaseProperties(){return{source:_source,browser_devtools_version:_browserDevtoolsVersion,node_version:process.version,os_platform:process.platform,os_arch:process.arch,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,timestamp:new Date().toUTCString()}}function init(opts={}){if(!_initialized){_initialized=!0;try{let config=readConfig();_anonymousId=config.anonymousId;let telemetryEnabled=process.env.TELEMETRY_ENABLE!=="false";if(_enabled=config.telemetryEnabled&&!opts.disabled&&telemetryEnabled,opts.source==="cli")_source=_detectCLISource();else if(opts.source==="mcp"){let detectedSource=_detectMcpClientSource(opts.clientInfo);_source=detectedSource==="cli"?"mcp-unknown":detectedSource}else _source=_detectMcpClientSource(opts.clientInfo);if(!config.telemetryNoticeShown&&telemetryEnabled&&(process.stderr.write(`
|
|
2
|
+
Telemetry is enabled by default to help improve this tool.
|
|
3
|
+
Run with --no-telemetry for CLI and set TELEMETRY_ENABLE=false for MCP Servers to disable.
|
|
4
|
+
|
|
5
|
+
`),updateConfig({telemetryNoticeShown:!0})),!_enabled)return;_client=new PostHog(POSTHOG_API_KEY,{host:POSTHOG_HOST,flushAt:20,flushInterval:1e4,disableGeoip:!1});let _beforeExitHandled=!1;process.on("beforeExit",async()=>{if(!(_beforeExitHandled||!_client)){_beforeExitHandled=!0;try{await _client.shutdown(),_client=null}catch{_client=null}}})}catch{}}}function trackToolCalled(opts){if(!(!_enabled||!_client))try{_source==="mcp-unknown"&&opts.clientName&&refineSource({name:opts.clientName});let properties={..._buildBaseProperties(),tool_name:opts.toolName,duration_ms:opts.durationMs,success:opts.success};if(opts.clientName&&(properties.client_name=opts.clientName),!opts.success&&opts.error!=null){let err=opts.error;properties.error_type=err?.constructor?.name??"Error",err?.code!==void 0&&(properties.error_code=err.code)}opts.sessionId&&(properties.session_id=opts.sessionId),_client.capture({distinctId:_anonymousId,event:"tool_called",properties})}catch{}}function refineSource(clientInfo){if(_source!=="mcp-unknown")return;let refined=_detectMcpClientSource(clientInfo);refined!=="mcp-unknown"&&(_source=refined)}async function shutdown(){if(_client){try{await _client.shutdown()}catch{}_client=null}}export{init,trackToolCalled,shutdown};
|