@agimon-ai/browse-tool 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/dist/cli.cjs +24 -23
- package/dist/cli.mjs +23 -23
- package/dist/extension/background.js +1039 -339
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/content.js +45 -4
- package/dist/extension/content.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -2
- package/dist/playwright-test-BwI7HgW7.mjs +1 -2
- package/dist/{playwright-test-CnsuVfC9.cjs → playwright-test-DTw_9rvK.cjs} +1 -1
- package/dist/stdio-DKou0TqY.cjs +10 -0
- package/dist/stdio-ELROpCD_.mjs +9 -0
- package/dist/stubs/playwright-test.cjs +1 -1
- package/package.json +12 -2
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.mts +0 -2
- package/dist/cli.mjs.map +0 -1
- package/dist/index.d.cts +0 -309
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts +0 -310
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/playwright-test-BwI7HgW7.mjs.map +0 -1
- package/dist/stdio-BP3yiSxK.mjs +0 -9
- package/dist/stdio-BP3yiSxK.mjs.map +0 -1
- package/dist/stdio-ynNFGBY4.cjs +0 -9
- package/dist/stubs/playwright-test.d.cts +0 -111
- package/dist/stubs/playwright-test.d.cts.map +0 -1
- package/dist/stubs/playwright-test.d.mts +0 -111
- package/dist/stubs/playwright-test.d.mts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,c as t,d as n,f as r,i,l as a,o,p as s,r as c,s as l,t as u,u as d}from"./stdio-BP3yiSxK.mjs";import"./playwright-test-BwI7HgW7.mjs";import"reflect-metadata/lite";import{Command as f,Option as p}from"commander";import{serve as m}from"@hono/node-server";import{Server as h}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as _,ListToolsRequestSchema as v}from"@modelcontextprotocol/sdk/types.js";import{Hono as y}from"hono";import{cors as b}from"hono/cors";import{Container as x,ContainerModule as S}from"inversify";import{existsSync as C,readFileSync as w,statSync as ee}from"node:fs";import{homedir as te}from"node:os";import T from"node:path";import{z as E}from"zod";import{PortRegistryService as ne}from"@agimon-ai/foundation-port-registry";import{createNodeWebSocket as re}from"@hono/node-ws";import{raw as D}from"hono/html";import{Fragment as ie,jsx as O,jsxs as k}from"hono/jsx/jsx-runtime";var ae=`0.2.0`;function A(e){let t=new y;return t.get(`/tasks`,t=>{try{let n=e.get(s.ExtensionTaskQueue).getNextTask();return n?t.json({task:{id:n.id,tool:n.tool,arguments:n.arguments}}):t.json({})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/result`,async t=>{try{let n=await t.req.json();if(!n.taskId)return t.json({success:!1,error:`Missing taskId in request body`},400);let r=e.get(s.ExtensionTaskQueue),i={taskId:n.taskId,success:n.success,result:n.result,error:n.error};return r.submitResult(i)?t.json({success:!0}):t.json({success:!1,error:`Task ${n.taskId} not found or already completed`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/status`,t=>{try{let n=e.get(s.ExtensionTaskQueue),r=n.getConnectionStatus(),i={connected:r.connected,lastPollAt:r.lastPollAt?.toISOString(),lastResultAt:r.lastResultAt?.toISOString(),pendingTasks:r.pendingTasks,queueSize:n.queueSize};return t.json(i)}catch(e){return t.json({connected:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/register`,async t=>{try{let n=await t.req.json();if(!n.browserId)return t.json({success:!1,error:`Missing browserId in request body`},400);let r=e.get(s.ExtensionSessionRegistry).register(n);return t.json({success:!0,session:{id:r.id,browserId:r.browserId,controlMode:r.controlMode,createdAt:r.createdAt.toISOString()}})}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/heartbeat`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(s.ExtensionSessionRegistry).heartbeat(n);return r?t.json({success:!0,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested,lastHeartbeatAt:r.lastHeartbeatAt.toISOString()}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(s.ExtensionSessionRegistry).requestHandoff(n);return r?t.json({success:!0,message:`Handoff requested. AI will take control when ready.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff/acknowledge`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(s.ExtensionSessionRegistry).acknowledgeHandoff(n.sessionId);return r?t.json({success:!0,message:`Handoff acknowledged. AI now has control.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found or no handoff pending`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/sessions`,t=>{try{let n=e.get(s.ExtensionSessionRegistry).listSessions();return t.json({sessions:n.map(e=>({id:e.id,browserId:e.browserId,tabId:e.tabId,currentUrl:e.currentUrl,controlMode:e.controlMode,activeSpecPath:e.activeSpecPath,handoffRequested:e.handoffRequested,createdAt:e.createdAt.toISOString(),lastHeartbeatAt:e.lastHeartbeatAt.toISOString()}))})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t}function oe(){return new S(e=>{e.bind(s.ExtensionTaskQueue).to(r).inSingletonScope(),e.bind(s.ExtensionToolDelegator).to(n).inSingletonScope()})}function se(e){let t=new h({name:`browse-tool-chrome`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getSupportedTools().map(e=>({name:e,description:`Browser automation tool (Chrome extension mode): ${e}`,inputSchema:{type:`object`,properties:{},additionalProperties:!0}}));return t.setRequestHandler(v,async()=>({tools:n})),t.setRequestHandler(_,async t=>{let{name:n,arguments:r}=t.params;return await e.executeTool(n,r||{})}),t}const ce=new f(`chrome-serve`).description(`[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation`).option(`-p, --port <port>`,`HTTP server port for extension polling`,`3200`).option(`-v, --verbose`,`Enable verbose output`,!1).option(`--wait-for-extension`,`Wait for extension to connect before accepting MCP requests`,!1).action(async e=>{console.error(``),console.error(`╔════════════════════════════════════════════════════════════════╗`),console.error(`║ DEPRECATED: chrome-serve is deprecated. ║`),console.error(`║ Use mcp-serve instead - extension routes are now ║`),console.error(`║ automatically available in the HTTP server at /extension/*. ║`),console.error(`╚════════════════════════════════════════════════════════════════╝`),console.error(``);try{let t=typeof e.port==`string`?Number.parseInt(e.port,10):e.port;e.verbose&&(console.error(`Chrome Extension MCP Server starting...`),console.error(` HTTP Port: ${t}`),console.error(` Wait for extension: ${e.waitForExtension}`));let n=new x({defaultScope:`Singleton`});n.load(oe());let r=n.get(s.ExtensionTaskQueue),i=n.get(s.ExtensionToolDelegator),a=new y;a.use(`*`,b({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`]}));let o=A(n);a.route(`/extension`,o),a.get(`/health`,e=>{let t=r.getConnectionStatus();return e.json({status:`healthy`,service:`browse-tool-chrome`,extension:t})});let c=m({fetch:a.fetch,port:t});c.on(`error`,e=>{e.code===`EADDRINUSE`?(console.error(`Error [PORT_IN_USE]: Port ${t} is already in use.`),console.error(`Recovery: Try a different port with --port <port>`)):console.error(`Error [SERVER_ERROR]: HTTP server error: ${e.message}`),process.exit(1)}),console.error(`HTTP server started on port ${t}`),console.error(`Waiting for Chrome extension to connect...`),console.error(`Extension should poll: http://localhost:${t}/extension/tasks`),e.waitForExtension&&(console.error(`Waiting for extension connection before starting MCP...`),await new Promise(e=>{let t=setInterval(()=>{r.getConnectionStatus().connected&&(clearInterval(t),console.error(`Extension connected!`),e())},500)}));let l=se(i),u=new g;await l.connect(u),console.error(`Chrome extension MCP server started on stdio`);let d=async e=>{console.error(`\nReceived ${e}, shutting down gracefully...`);try{r.clearAllTasks(`Server shutting down`),await u.close(),c.close(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>d(`SIGINT`)),process.on(`SIGTERM`,()=>d(`SIGTERM`))}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${t}`),console.error(`Recovery: Check that the port is available and try again.`),process.exit(1)}}),le=`BROWSE_TOOL_CONFIG`,ue=`.browse-tool`,j=`config.json`,de=[`json`,`text`,`quiet`],fe={commands:{},tools:{}},pe=E.record(E.string(),E.unknown()),me=E.object({mcpServe:E.object({type:E.string().optional(),browser:E.string().optional(),headless:E.boolean().optional(),profile:E.string().optional(),mode:E.string().optional(),host:E.string().optional(),tags:E.string().optional(),exclude:E.string().optional(),registryPath:E.string().optional(),registryDir:E.string().optional(),pidsDir:E.string().optional(),profilesDir:E.string().optional()}).partial().optional(),httpServe:E.object({port:E.coerce.number().int().positive().optional(),headless:E.boolean().optional(),idleTimeout:E.coerce.number().positive().optional(),host:E.string().optional(),registryDir:E.string().optional(),registryPath:E.string().optional(),pidsDir:E.string().optional(),profilesDir:E.string().optional()}).partial().optional(),exec:E.object({format:E.enum(de).optional(),color:E.boolean().optional(),port:E.coerce.number().int().positive().optional()}).partial().optional(),tools:E.object({format:E.enum(de).optional(),color:E.boolean().optional(),port:E.coerce.number().int().positive().optional()}).partial().optional(),status:E.record(E.string(),E.unknown()).optional(),stop:E.record(E.string(),E.unknown()).optional()}),he=E.object({commands:me.default({}),tools:E.record(E.string(),pe).default({})});let M={config:fe};function ge(e){for(let t=0;t<e.length;t+=1){let n=e[t];if(n===`--config`)return e[t+1];if(n.startsWith(`--config=`))return n.slice(9)}}function N(e){let t=T.resolve(e);if(!C(t))throw Error(`Config path not found: ${t}`);return ee(t).isDirectory()?T.join(t,j):t}function _e(e,t){let n=t.env??process.env,r=t.cwd??process.cwd(),i=t.homeDir??te(),a=ge(e);if(a)return N(a);if(n[le])return N(n[le]);let o=T.join(r,ue,j);if(C(o))return o;let s=T.join(i,ue,j);if(C(s))return s}function ve(e){let t=w(e,`utf8`),n=JSON.parse(t);return he.parse(n)}function ye(e,t={}){let n=_e(e,t);return n?{configPath:n,config:ve(n)}:{config:fe}}function be(e,t={}){return M=ye(e,t),M}function P(e){return(M.config.commands??{})[e]??{}}function xe(e){return M.config.tools?.[e]??{}}function F(e,t,n,r,i){let a=e.getOptionValueSourceWithGlobals(t);return a!==void 0&&a!==`default`&&a!==`implied`?n:i===void 0?r===void 0?n:r:i}var Se=class{port;timeout;serverPort=null;constructor(e={}){this.port=e.port??3200,this.timeout=e.timeout??6e4}async ensureServer(){if(this.serverPort!==null)return this.serverPort;let t=await e().get(s.HttpServerManager).ensureRunning(this.port);if(!t.running||!t.port)throw Error(t.error??`Failed to start HTTP server. Try running "browse-tool http-serve" manually.`);return this.serverPort=t.port,t.port}async getBaseUrl(){return`http://localhost:${await this.ensureServer()}`}async listTools(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/tools`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);return(await n.json()).tools}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async listBrowsers(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/browsers`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);return r.browsers}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async execute(e,t){let n=await this.getBaseUrl(),r=e===`run_spec`?void 0:this.timeout,i=new AbortController,a=r===void 0?void 0:setTimeout(()=>i.abort(),r);try{let a=await fetch(`${n}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify({tool:e,arguments:t}),signal:r===void 0?void 0:i.signal});if(!a.ok){let e=await a.text();throw Error(`HTTP ${a.status}: ${e||a.statusText}`)}let o=await a.json();return o.success?o.result??{content:[{type:`text`,text:`No result returned`}]}:{content:[{type:`text`,text:o.error??`Unknown error`}],isError:!0}}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`):this.wrapConnectionError(e)}finally{a&&clearTimeout(a)}}wrapConnectionError(e){return e instanceof Error?e.message.includes(`ECONNREFUSED`)||e.message.includes(`fetch failed`)?Error(`Cannot connect to browse-tool HTTP server.\n\nTry one of the following:\n 1. Start the server: browse-tool http-serve\n 2. Check if another process is using port ${this.port}\n 3. Run with a different port: browse-tool --port 3201 <command>\n\nOriginal error: ${e.message}`):e:Error(String(e))}};function I(e){return new Se(e)}const L={reset:`\x1B[0m`,red:`\x1B[31m`,green:`\x1B[32m`,yellow:`\x1B[33m`,blue:`\x1B[34m`,magenta:`\x1B[35m`,cyan:`\x1B[36m`,gray:`\x1B[90m`,bold:`\x1B[1m`};function R(e,t,n){return n?`${L[t]}${e}${L.reset}`:e}function z(e,t){let{format:n,color:r}=t;return n===`quiet`?``:n===`json`?Ce(e):we(e,r)}function Ce(e){return JSON.stringify(e,null,2)}function we(e,t){let n=[];e.isError&&n.push(R(`Error:`,`red`,t));for(let r of e.content)r.type===`text`?n.push(Te(r,t)):r.type===`image`?n.push(Oe(r,t)):n.push(R(`[Unknown content type: ${r.type}]`,`yellow`,t));return n.join(`
|
|
3
|
-
`)}function
|
|
4
|
-
`)}function
|
|
2
|
+
import{a as e,c as t,d as n,f as r,g as i,h as a,i as o,l as s,m as c,o as l,p as u,r as d,s as f,t as p,u as m}from"./stdio-ELROpCD_.mjs";import"./playwright-test-BwI7HgW7.mjs";import{stripTypeScriptTypes as h}from"node:module";import"reflect-metadata/lite";import{Command as g,Option as _}from"commander";import{serve as ee}from"@hono/node-server";import{Server as v}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as b,ListToolsRequestSchema as x}from"@modelcontextprotocol/sdk/types.js";import{Hono as S}from"hono";import{cors as C}from"hono/cors";import{Container as te,ContainerModule as ne}from"inversify";import{existsSync as w,readFileSync as re,statSync as ie}from"node:fs";import T from"node:path";import{SpanStatusCode as E}from"@opentelemetry/api";import{PortRegistryService as ae}from"@agimon-ai/foundation-port-registry";import{homedir as oe}from"node:os";import{z as D}from"zod";import{readFile as se,stat as ce}from"node:fs/promises";import{createNodeWebSocket as le}from"@hono/node-ws";import{raw as ue}from"hono/html";import{Fragment as de,jsx as O,jsxs as k}from"hono/jsx/jsx-runtime";var fe=`0.2.1`;function pe(e){let t=new S;return t.get(`/telemetry-config`,e=>{try{let t=new URL(e.req.url).origin;return e.json(a(process.env,t))}catch(t){return e.json({enabled:!1,error:t instanceof Error?t.message:String(t)},500)}}),t.get(`/tasks`,t=>{try{let n=e.get(i.ExtensionTaskQueue).getNextTask();return n?t.json({task:{id:n.id,tool:n.tool,arguments:n.arguments,telemetry:n.telemetry}}):t.json({})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/result`,async t=>{try{let n=await t.req.json();if(!n.taskId)return t.json({success:!1,error:`Missing taskId in request body`},400);let r=e.get(i.ExtensionTaskQueue),a={taskId:n.taskId,success:n.success,result:n.result,error:n.error};return r.submitResult(a)?t.json({success:!0}):t.json({success:!1,error:`Task ${n.taskId} not found or already completed`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/status`,t=>{try{let n=e.get(i.ExtensionTaskQueue),r=n.getConnectionStatus(),a={connected:r.connected,lastPollAt:r.lastPollAt?.toISOString(),lastResultAt:r.lastResultAt?.toISOString(),pendingTasks:r.pendingTasks,queueSize:n.queueSize};return t.json(a)}catch(e){return t.json({connected:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/register`,async t=>{try{let n=await t.req.json();if(!n.browserId)return t.json({success:!1,error:`Missing browserId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).register(n);return t.json({success:!0,session:{id:r.id,browserId:r.browserId,controlMode:r.controlMode,createdAt:r.createdAt.toISOString()}})}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/heartbeat`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).heartbeat(n);return r?t.json({success:!0,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested,lastHeartbeatAt:r.lastHeartbeatAt.toISOString()}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).requestHandoff(n);return r?t.json({success:!0,message:`Handoff requested. AI will take control when ready.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff/acknowledge`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).acknowledgeHandoff(n.sessionId);return r?t.json({success:!0,message:`Handoff acknowledged. AI now has control.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found or no handoff pending`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/sessions`,t=>{try{let n=e.get(i.ExtensionSessionRegistry).listSessions();return t.json({sessions:n.map(e=>({id:e.id,browserId:e.browserId,tabId:e.tabId,currentUrl:e.currentUrl,controlMode:e.controlMode,activeSpecPath:e.activeSpecPath,handoffRequested:e.handoffRequested,createdAt:e.createdAt.toISOString(),lastHeartbeatAt:e.lastHeartbeatAt.toISOString()}))})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t}function me(){return new ne(e=>{e.bind(i.ExtensionTaskQueue).to(u).inSingletonScope(),e.bind(i.ExtensionToolDelegator).to(r).inSingletonScope()})}function he(e){let t=new v({name:`browse-tool-chrome`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getSupportedTools().map(e=>({name:e,description:`Browser automation tool (Chrome extension mode): ${e}`,inputSchema:{type:`object`,properties:{},additionalProperties:!0}}));return t.setRequestHandler(x,async()=>({tools:n})),t.setRequestHandler(b,async t=>{let{name:n,arguments:r}=t.params;return await e.executeTool(n,r||{})}),t}const ge=new g(`chrome-serve`).description(`[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation`).option(`-p, --port <port>`,`HTTP server port for extension polling`,`3200`).option(`-v, --verbose`,`Enable verbose output`,!1).option(`--wait-for-extension`,`Wait for extension to connect before accepting MCP requests`,!1).action(async e=>{console.error(``),console.error(`╔════════════════════════════════════════════════════════════════╗`),console.error(`║ DEPRECATED: chrome-serve is deprecated. ║`),console.error(`║ Use mcp-serve instead - extension routes are now ║`),console.error(`║ automatically available in the HTTP server at /extension/*. ║`),console.error(`╚════════════════════════════════════════════════════════════════╝`),console.error(``);try{let t=typeof e.port==`string`?Number.parseInt(e.port,10):e.port;e.verbose&&(console.error(`Chrome Extension MCP Server starting...`),console.error(` HTTP Port: ${t}`),console.error(` Wait for extension: ${e.waitForExtension}`));let n=new te({defaultScope:`Singleton`});n.load(me());let r=n.get(i.ExtensionTaskQueue),a=n.get(i.ExtensionToolDelegator),o=new S;o.use(`*`,C({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`]}));let s=pe(n);o.route(`/extension`,s),o.get(`/health`,e=>{let t=r.getConnectionStatus();return e.json({status:`healthy`,service:`browse-tool-chrome`,extension:t})});let c=ee({fetch:o.fetch,port:t});c.on(`error`,e=>{e.code===`EADDRINUSE`?(console.error(`Error [PORT_IN_USE]: Port ${t} is already in use.`),console.error(`Recovery: Try a different port with --port <port>`)):console.error(`Error [SERVER_ERROR]: HTTP server error: ${e.message}`),process.exit(1)}),console.error(`HTTP server started on port ${t}`),console.error(`Waiting for Chrome extension to connect...`),console.error(`Extension should poll: http://localhost:${t}/extension/tasks`),e.waitForExtension&&(console.error(`Waiting for extension connection before starting MCP...`),await new Promise(e=>{let t=setInterval(()=>{r.getConnectionStatus().connected&&(clearInterval(t),console.error(`Extension connected!`),e())},500)}));let l=he(a),u=new y;await l.connect(u),console.error(`Chrome extension MCP server started on stdio`);let d=async e=>{console.error(`\nReceived ${e}, shutting down gracefully...`);try{r.clearAllTasks(`Server shutting down`),await u.close(),c.close(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>d(`SIGINT`)),process.on(`SIGTERM`,()=>d(`SIGTERM`))}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${t}`),console.error(`Recovery: Check that the port is available and try again.`),process.exit(1)}}),_e=`BROWSE_TOOL_CONFIG`,ve=`.browse-tool`,A=`config.json`,ye=[`json`,`text`,`quiet`],be={commands:{},tools:{}},xe=D.record(D.string(),D.unknown()),Se=D.object({mcpServe:D.object({type:D.string().optional(),browser:D.string().optional(),headless:D.boolean().optional(),profile:D.string().optional(),mode:D.string().optional(),host:D.string().optional(),tags:D.string().optional(),exclude:D.string().optional(),customTools:D.string().optional(),registryPath:D.string().optional(),registryDir:D.string().optional(),pidsDir:D.string().optional(),profilesDir:D.string().optional()}).partial().optional(),httpServe:D.object({port:D.coerce.number().int().positive().optional(),headless:D.boolean().optional(),idleTimeout:D.coerce.number().positive().optional(),host:D.string().optional(),registryDir:D.string().optional(),registryPath:D.string().optional(),pidsDir:D.string().optional(),profilesDir:D.string().optional()}).partial().optional(),exec:D.object({format:D.enum(ye).optional(),color:D.boolean().optional(),port:D.coerce.number().int().positive().optional()}).partial().optional(),tools:D.object({format:D.enum(ye).optional(),color:D.boolean().optional(),port:D.coerce.number().int().positive().optional()}).partial().optional(),status:D.record(D.string(),D.unknown()).optional(),stop:D.record(D.string(),D.unknown()).optional()}),Ce=D.object({commands:Se.default({}),tools:D.record(D.string(),xe).default({})});let j={config:be};function we(e){for(let t=0;t<e.length;t+=1){let n=e[t];if(n===`--config`)return e[t+1];if(n.startsWith(`--config=`))return n.slice(9)}}function Te(e){let t=T.resolve(e);if(!w(t))throw Error(`Config path not found: ${t}`);return ie(t).isDirectory()?T.join(t,A):t}function Ee(e,t){let n=t.env??process.env,r=t.cwd??process.cwd(),i=t.homeDir??oe(),a=we(e);if(a)return Te(a);if(n[_e])return Te(n[_e]);let o=T.join(r,ve,A);if(w(o))return o;let s=T.join(i,ve,A);if(w(s))return s}function De(e){let t=re(e,`utf8`),n=JSON.parse(t);return Ce.parse(n)}function Oe(e,t={}){let n=Ee(e,t);return n?{configPath:n,config:De(n)}:{config:be}}function ke(e,t={}){return j=Oe(e,t),j}function M(e){return(j.config.commands??{})[e]??{}}function Ae(e){return j.config.tools?.[e]??{}}function N(e,t,n,r,i){let a=e.getOptionValueSourceWithGlobals(t);return a!==void 0&&a!==`default`&&a!==`implied`?n:i===void 0?r===void 0?n:r:i}var je=class{port;timeout;serverPort=null;constructor(e={}){this.port=e.port??3200,this.timeout=e.timeout??6e4}async ensureServer(){if(this.serverPort!==null)return this.serverPort;let t=await e().get(i.HttpServerManager).ensureRunning(this.port);if(!t.running||!t.port)throw Error(t.error??`Failed to start HTTP server. Try running "browse-tool http-serve" manually.`);return this.serverPort=t.port,t.port}async getBaseUrl(){return`http://localhost:${await this.ensureServer()}`}async listTools(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/tools`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);return r.tools}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async listCustomTools(e){let t=await this.getBaseUrl(),n=new AbortController,r=setTimeout(()=>n.abort(),this.timeout);try{let r=new URL(`/custom-tools`,t);r.searchParams.set(`dir`,e);let i=await fetch(r,{method:`GET`,headers:{Accept:`application/json`},signal:n.signal});if(!i.ok)throw Error(`HTTP ${i.status}: ${i.statusText}`);let a=await i.json();if(a.error)throw Error(a.error);return a.tools}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(r)}}async listBrowsers(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/browsers`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);return r.browsers}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async execute(e,t){let n=await this.getBaseUrl(),r=e===`run_spec`?void 0:this.timeout,i=new AbortController,a=r===void 0?void 0:setTimeout(()=>i.abort(),r);try{let a=await fetch(`${n}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify({tool:e,arguments:t}),signal:r===void 0?void 0:i.signal});if(!a.ok){let e=await a.text();throw Error(`HTTP ${a.status}: ${e||a.statusText}`)}let o=await a.json();return o.success?o.result??{content:[{type:`text`,text:`No result returned`}]}:{content:[{type:`text`,text:o.error??`Unknown error`}],isError:!0}}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{a&&clearTimeout(a)}}async executeCustomTool(e,t,n){let r=await this.getBaseUrl(),i=new AbortController,a=setTimeout(()=>i.abort(),this.timeout);try{let a=new URL(`/custom-tools`,r);a.searchParams.set(`dir`,e);let o=await fetch(a,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify({tool:t,arguments:n}),signal:i.signal});if(!o.ok){let e=await o.text();throw Error(`HTTP ${o.status}: ${e||o.statusText}`)}let s=await o.json();return s.success?s.result??{content:[{type:`text`,text:`No result returned`}]}:{content:[{type:`text`,text:s.error??`Unknown error`}],isError:!0}}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(a)}}wrapConnectionError(e){return e instanceof Error?e.message.includes(`ECONNREFUSED`)||e.message.includes(`fetch failed`)?Error(`Cannot connect to browse-tool HTTP server.\n\nTry one of the following:\n 1. Start the server: browse-tool http-serve\n 2. Check if another process is using port ${this.port}\n 3. Run with a different port: browse-tool --port 3201 <command>\n\nOriginal error: ${e.message}`,{cause:e}):e:Error(String(e),{cause:e})}};function P(e){return new je(e)}const Me={reset:`\x1B[0m`,red:`\x1B[31m`,green:`\x1B[32m`,yellow:`\x1B[33m`,blue:`\x1B[34m`,magenta:`\x1B[35m`,cyan:`\x1B[36m`,gray:`\x1B[90m`,bold:`\x1B[1m`};function F(e,t,n){return n?`${Me[t]}${e}${Me.reset}`:e}function I(e,t){let{format:n,color:r}=t;return n===`quiet`?``:n===`json`?Ne(e):Pe(e,r)}function Ne(e){return JSON.stringify(e,null,2)}function Pe(e,t){let n=[];e.isError&&n.push(F(`Error:`,`red`,t));for(let r of e.content)r.type===`text`?n.push(Fe(r,t)):r.type===`image`?n.push(Re(r,t)):n.push(F(`[Unknown content type: ${r.type}]`,`yellow`,t));return n.join(`
|
|
3
|
+
`)}function Fe(e,t){let n=e.text;try{return Ie(JSON.parse(n),t)}catch{return n}}function Ie(e,t){if(typeof e!=`object`||!e)return String(e);let n=[];for(let[r,i]of Object.entries(e)){let e=F(r,`cyan`,t),a=Le(i,t);n.push(`${e}: ${a}`)}return n.join(`
|
|
4
|
+
`)}function Le(e,t){return e===null?F(`null`,`gray`,t):e===void 0?F(`undefined`,`gray`,t):typeof e==`boolean`?F(String(e),e?`green`:`red`,t):typeof e==`number`?F(String(e),`yellow`,t):typeof e==`string`?e.startsWith(`http://`)||e.startsWith(`https://`)?F(e,`blue`,t):e:Array.isArray(e)?e.length===0?F(`[]`,`gray`,t):JSON.stringify(e,null,2):typeof e==`object`?JSON.stringify(e,null,2):String(e)}function Re(e,t){let{mimeType:n,data:r}=e;return F(`[Image: ${n}, ~${Math.round(r.length*3/4/1024)}KB base64 data]`,`magenta`,t)}function L(e,t){return F(`Error: ${e instanceof Error?e.message:e}`,`red`,t)}function ze(e,t){let n=Math.max(...e.map(e=>e.name.length)),r=[];for(let i of e){let e=F(i.name.padEnd(n),`cyan`,t);r.push(` ${e} ${i.description}`)}return r.join(`
|
|
5
|
+
`)}function Be(e,t){let n=M(`tools`),r=N(e,`format`,t.format,n.format),i=N(e,`color`,t.color,n.color);return{port:N(e,`port`,t.port,n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),formatterOptions:{format:r,color:i}}}const Ve=new g(`list-custom-tools`).description(`List custom tools from a tools directory`).argument(`<dir>`,`Path to the tools directory containing tools.yaml`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(f)).action(async function(e,t){let{port:n,formatterOptions:r}=Be(this,t);try{let t=await P({port:Number.parseInt(n,10)}).listCustomTools(e),i=r.format===`text`?ze(t,r.color):r.format===`quiet`?``:JSON.stringify(t,null,2);i&&console.log(i)}catch(e){console.error(L(e instanceof Error?e:String(e),r.color)),process.exit(1)}}),He=new g(`exec-custom-tool`).description(`Execute a custom tool from a tools directory`).argument(`<dir>`,`Path to the tools directory containing tools.yaml`).argument(`<tool>`,`Custom tool name to execute`).argument(`[args]`,`JSON arguments for the tool`,`{}`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(f)).action(async function(e,t,n,r){let{port:i,formatterOptions:a}=Be(this,r);try{let r;try{r=JSON.parse(n)}catch{console.error(L(`Invalid JSON arguments: ${n}`,a.color)),process.exit(1);return}let o=await P({port:Number.parseInt(i,10)}).executeCustomTool(e,t,r),s=I(o,a);s&&console.log(s),o.isError&&process.exit(1)}catch(e){console.error(L(e instanceof Error?e:String(e),a.color)),process.exit(1)}});new g(`custom-tools`).description(`List and execute custom tools through the HTTP server`).addCommand(Ve).addCommand(He);const Ue=new g(`exec`).description(`Execute a tool directly with JSON arguments`).argument(`<tool>`,`Tool name to execute (e.g., browser_launch)`).argument(`[args]`,`JSON arguments for the tool`,`{}`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(f)).action(async function(e,t,n){let r=M(`exec`),i=N(this,`format`,n.format,r.format),a=N(this,`color`,n.color,r.color),o=N(this,`port`,n.port,r.port===void 0?void 0:String(r.port),process.env.PLAYWRIGHT_PORT),s={format:i,color:a};try{let n;try{n=JSON.parse(t)}catch{console.error(L(`Invalid JSON arguments: ${t}`,s.color)),process.exit(1)}let r=await P({port:Number.parseInt(o,10)}).execute(e,n),i=I(r,s);i&&console.log(i),r.isError&&process.exit(1)}catch(e){console.error(L(e instanceof Error?e:String(e),s.color)),process.exit(1)}}),R=`pageId`,We=D.record(D.string(),D.unknown()),Ge=D.object({type:D.literal(`object`),properties:D.record(D.string(),D.unknown()).optional(),required:D.array(D.string()).optional(),additionalProperties:D.boolean().optional()}).passthrough(),Ke=D.object({name:D.string().min(1),description:D.string().min(1),script:D.string().min(1),suggestionActions:D.string().min(1).optional(),capabilities:We,inputSchema:Ge}),qe=D.object({tools:D.array(Ke)}),Je=process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS===`1`;function Ye(e){return JSON.stringify(e,(e,t)=>typeof t==`string`&&t.length>240?`${t.slice(0,240)}...<trimmed>`:t)}function z(e,t){if(Je){if(t){console.error(`[CustomToolService] ${e}`,t);return}console.error(`[CustomToolService] ${e}`)}}function B(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Xe(e){return B(e)&&Array.isArray(e.content)}function V(e){let t=e.trim();return t===``?``:t.startsWith(`"`)&&t.endsWith(`"`)||t.startsWith(`[`)&&t.endsWith(`]`)||t.startsWith(`{`)&&t.endsWith(`}`)?JSON.parse(t):t.startsWith(`'`)&&t.endsWith(`'`)?t.slice(1,-1):t===`true`?!0:t===`false`?!1:t===`null`?null:/^-?\d+(\.\d+)?$/.test(t)?Number(t):t}function Ze(e){if(!e||Object.keys(e).length===0)return;let t={};for(let[n,r]of Object.entries(e))if(r!=null){if(typeof r==`string`||typeof r==`number`||typeof r==`boolean`){t[n]=r;continue}t[n]=JSON.stringify(r)}return Object.keys(t).length>0?t:void 0}function Qe(e){return e.replace(/^\uFEFF/,``).split(/\r?\n/).map(e=>{if(e.includes(` `))throw Error(`Tab indentation is not supported in tools.yaml`);let t=e.replace(/\s+#.*$/,``);return t.trim().length===0?null:{indent:t.match(/^ */)?.[0].length??0,text:t.trim()}}).filter(e=>e!==null)}function H(e,t,n){let r=e[t];if(!r||r.indent!==n)throw Error(`Invalid indentation in tools.yaml at line ${t+1}`);return r.text.startsWith(`-`)?$e(e,t,n):et(e,t,n)}function $e(e,t,n){let r=[],i=t;for(;i<e.length;){let t=e[i];if(t.indent<n||t.indent!==n||!t.text.startsWith(`-`))break;let a=t.text.slice(1).trim();if(a===``){let t=e[i+1];if(!t||t.indent<=n){r.push(null),i+=1;continue}let[a,o]=H(e,i+1,t.indent);r.push(a),i=o;continue}if(a.includes(`:`)){let[t,o]=U(a),s={};if(o===void 0){let r=e[i+1];if(!r||r.indent<=n)throw Error(`Expected nested value for "${t}" in tools.yaml`);let[a,o]=H(e,i+1,r.indent);s[t]=a,i=o}else s[t]=V(o),i+=1;for(;i<e.length&&e[i].indent>n;){let t=e[i];if(t.indent!==n+2||t.text.startsWith(`-`)){let[n,r]=H(e,i,t.indent);if(!B(n))throw Error(`Expected object entry in tools.yaml at line ${i+1}`);Object.assign(s,n),i=r;continue}let[r,a]=U(t.text);if(a===void 0){let n=e[i+1];if(!n||n.indent<=t.indent)throw Error(`Expected nested value for "${r}" in tools.yaml`);let[a,o]=H(e,i+1,n.indent);s[r]=a,i=o;continue}s[r]=V(a),i+=1}r.push(s);continue}r.push(V(a)),i+=1}return[r,i]}function et(e,t,n){let r={},i=t;for(;i<e.length;){let t=e[i];if(t.indent<n||t.indent!==n||t.text.startsWith(`-`))break;let[a,o]=U(t.text);if(o===void 0){let t=e[i+1];if(!t||t.indent<=n){r[a]=null,i+=1;continue}let[o,s]=H(e,i+1,t.indent);r[a]=o,i=s;continue}r[a]=V(o),i+=1}return[r,i]}function U(e){let t=e.indexOf(`:`);if(t===-1)throw Error(`Invalid tools.yaml entry: "${e}"`);let n=e.slice(0,t).trim(),r=e.slice(t+1).trim();return[n,r===``?void 0:r]}function tt(e){let t=Qe(e);if(t.length===0)return{};let[n]=H(t,0,t[0].indent);return n}function nt(e){let t=e.inputSchema.properties,n=e.inputSchema.required??[];if(!t||!B(t))throw Error(`Custom tool "${e.name}" must define inputSchema.properties`);let r=t[R];if(!B(r)||r.type!==`string`)throw Error(`Custom tool "${e.name}" must define inputSchema.properties.pageId as type "string"`);if(!n.includes(R))throw Error(`Custom tool "${e.name}" must require pageId in inputSchema.required`)}async function rt(e){let t=h(await se(e,`utf8`),{mode:`strip`});return await import(`data:text/javascript;base64,${Buffer.from(t,`utf8`).toString(`base64`)}`)}function it(e,t){let n=typeof t.default==`function`?t.default:void 0;if(!n)throw Error(`Custom tool script "${e}" must export a default function`);return{execute:n}}function at(e,t){return typeof e.suggestionActions==`string`&&e.suggestionActions.trim().length>0?e:{...e,suggestionActions:t}}function ot(e,t){let n=e.content[0];if(n?.type===`text`&&typeof n.text==`string`)try{let r=JSON.parse(n.text);if(B(r)){let i=at(r,t);return{...e,content:[{...n,text:JSON.stringify(i,null,2)},...e.content.slice(1)]}}}catch{}return{...e,content:[...e.content,{type:`text`,text:`suggestionActions: ${t}`}]}}function st(e,t,n){if(Xe(t))return n?ot(t,n):t;if(typeof t==`string`)return n?{content:[{type:`text`,text:JSON.stringify({result:t,suggestionActions:n},null,2)}]}:{content:[{type:`text`,text:t}]};if(t===void 0)return n?{content:[{type:`text`,text:JSON.stringify({result:`Custom tool "${e}" completed successfully`,suggestionActions:n},null,2)}]}:{content:[{type:`text`,text:`Custom tool "${e}" completed successfully`}]};let r=B(t)&&n?at(t,n):t;return{content:[{type:`text`,text:JSON.stringify(r,null,2)}]}}var ct=class{constructor(e,t,n=new c){this.pageRegistry=e,this.extensionTaskQueue=t,this.telemetry=n}resolveToolPage(e,t,r){if(r.page)return r.page;if(r.mode===`extension`&&this.extensionTaskQueue){let e=new n(this.extensionTaskQueue);return e.setTarget(t,r.browserId),e}throw Error(`Custom tool "${e}" requires a supported page context`)}createToolLogger(e,t,n){let r={"browse_tool.tool.name":e,"browse_tool.page.id":t,"browse_tool.browser.id":n.browserId,"browse_tool.execution.mode":n.mode},i=(e,t,n)=>{this.telemetry.log(e,t,{attributes:Ze({...r,...n?.attributes??{}}),exception:n?.exception})};return{getTraceContext:()=>this.telemetry.getActiveTraceContext(),trace:(e,t)=>i(`trace`,e,t),debug:(e,t)=>i(`debug`,e,t),info:(e,t)=>i(`info`,e,t),warn:(e,t)=>i(`warn`,e,t),error:(e,t)=>i(`error`,e,t),fatal:(e,t)=>i(`fatal`,e,t)}}async listTools(e){return(await this.loadTools(e)).map(({name:e,description:t,suggestionActions:n,inputSchema:r,capabilities:i})=>({name:e,description:t,suggestionActions:n,inputSchema:r,capabilities:i}))}async executeTool(e,t,n){let r=typeof n[R]==`string`?n[R]:void 0;return this.telemetry.runInSpan(`browse_tool.custom_tool.execute`,{attributes:{"browse_tool.tool.name":t,"browse_tool.custom_tools.directory":T.resolve(e),"browse_tool.page.id":r}},async r=>{let i=(await this.loadTools(e)).find(e=>e.name===t);if(!i)throw r?.setStatus({code:E.ERROR,message:`Custom tool "${t}" not found`}),Error(`Custom tool "${t}" not found`);let a=n[R];if(typeof a!=`string`||a.length===0)throw r?.setStatus({code:E.ERROR,message:`Custom tool "${t}" requires a string pageId`}),Error(`Custom tool "${t}" requires a string pageId`);let o=this.pageRegistry.get(a);if(!o)throw r?.setStatus({code:E.ERROR,message:`Page "${a}" not found`}),Error(`Page "${a}" not found`);let s=this.resolveToolPage(t,a,o),c=this.createToolLogger(t,a,o);r?.setAttributes({"browse_tool.browser.id":o.browserId,"browse_tool.execution.mode":o.mode}),z(`Executing custom tool`,{toolName:t,directory:T.resolve(e),pageId:a,browserId:o.browserId,mode:o.mode,input:Ye(n),scriptPath:i.scriptPath});let l;try{l=await i.execute?.({page:s,input:n,logger:c})}catch(e){let n=e instanceof Error?e.message:String(e);throw z(`Custom tool execution failed`,{toolName:t,pageId:a,browserId:o.browserId,mode:o.mode,error:n,stack:e instanceof Error?e.stack:void 0}),Error(`Custom tool "${t}" failed on page "${a}": ${n}`,{cause:e})}z(`Custom tool execution completed`,{toolName:t,pageId:a,browserId:o.browserId,mode:o.mode,result:Ye(l)});let u=st(t,l,i.suggestionActions),d=u.isError?u.content[0]?.text:void 0;return d&&r?.setStatus({code:E.ERROR,message:d}),u})}async loadTools(e){let t=T.resolve(e),n=T.join(t,`tools.yaml`),r=tt(await se(n,`utf8`).catch(e=>{throw Error(`Failed to read custom tool manifest at "${n}": ${e instanceof Error?e.message:String(e)}`)})),i=qe.parse(r);return Promise.all(i.tools.map(async e=>{nt(e);let n=T.resolve(t,e.script);await ce(n).catch(t=>{throw Error(`Custom tool "${e.name}" script not found at "${n}": ${t instanceof Error?t.message:String(t)}`)});let r=it(n,await rt(n));return{name:e.name,description:e.description,suggestionActions:e.suggestionActions,inputSchema:e.inputSchema,capabilities:e.capabilities,scriptPath:n,...r}}))}};function lt(e){let t=new S;return t.get(`/browsers`,async t=>{try{let n=e.get(i.BrowserService),r=e.get(i.PageRegistry),a=n.listBrowsers(),o=r.list(),s=a.map(e=>{let t=o.filter(t=>t.browserId===e.id);return{id:e.id,profileName:e.profileName,currentPageId:e.currentPageId,createdAt:e.createdAt.toISOString(),pages:t.map(e=>({id:e.id,url:e.url,title:e.title,createdAt:e.createdAt.toISOString()}))}}),c={totalBrowsers:a.length,totalPages:o.length};return t.json({browsers:s,stats:c})}catch(e){return console.error(`Failed to list browsers:`,e),t.json({error:`Failed to list browsers`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/browsers`,async t=>{try{return await e.get(i.BrowserService).closeAll(),t.json({success:!0,message:`All browsers closed`})}catch(e){return console.error(`Failed to close all browsers:`,e),t.json({error:`Failed to close all browsers`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/browsers/:id`,async t=>{try{let n=t.req.param(`id`),r=e.get(i.BrowserService);return r.getBrowser(n)?(await r.closeBrowser(n),t.json({success:!0,message:`Browser "${n}" closed`})):t.json({error:`Browser "${n}" not found`},404)}catch(e){return console.error(`Failed to close browser:`,e),t.json({error:`Failed to close browser`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/pages/:id`,async t=>{try{let n=t.req.param(`id`),r=e.get(i.PageRegistry).get(n);return r?r.page?(await r.page.close(),t.json({success:!0,message:`Page "${n}" closed`})):t.json({error:`Page "${n}" is in extension mode and cannot be closed via API`},400):t.json({error:`Page "${n}" not found`},404)}catch(e){return console.error(`Failed to close page:`,e),t.json({error:`Failed to close page`,message:e instanceof Error?e.message:String(e)},500)}}),t}function ut(e){return e.toLocaleString(`en-US`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1})}function dt(e){let t=Math.floor(e/1e3),n=Math.floor(t/60),r=Math.floor(n/60);return r>0?`${r}h ${n%60}m`:n>0?`${n}m ${t%60}s`:`${t}s`}function ft(e){return dt(new Date().getTime()-e.getTime())}const W=`table-container`,G=`stat-card`,pt=`browser-row`,mt=`page-row`,K=`status-active`,ht=`url-cell`,q=`timestamp-cell`,J=`actions-cell`,Y=`kill-btn`,gt=`empty-state`;function _t({browsers:e}){return e.length===0?O(`div`,{class:W,children:k(`div`,{class:gt,children:[O(`h3`,{children:`No Active Browsers`}),O(`p`,{children:`Launch a browser using MCP tools to see it here.`})]})}):O(`div`,{class:W,children:k(`table`,{class:`browser-table`,children:[O(`thead`,{children:k(`tr`,{children:[O(`th`,{children:`ID`}),O(`th`,{children:`Status`}),O(`th`,{children:`URL / Title`}),O(`th`,{children:`Created`}),O(`th`,{children:`Age`}),O(`th`,{children:`Actions`})]})}),O(`tbody`,{id:`browser-table-body`,children:e.map(e=>k(de,{children:[k(`tr`,{class:pt,children:[O(`td`,{children:e.id}),O(`td`,{children:k(`span`,{class:K,children:[e.pages.length,` page`,e.pages.length===1?``:`s`]})}),O(`td`,{children:e.profileName||`Default Profile`}),O(`td`,{class:q,children:ut(e.createdAt)}),O(`td`,{class:q,children:ft(e.createdAt)}),O(`td`,{class:J,children:O(`button`,{class:Y,onclick:`dashboard.killBrowser('${e.id}')`,children:`Kill`})})]}),e.pages.map(t=>k(`tr`,{class:mt,children:[k(`td`,{children:[t.id,e.currentPageId===t.id&&` (active)`]}),O(`td`,{children:O(`span`,{class:K,children:`Open`})}),O(`td`,{class:ht,title:t.url,children:t.title||t.url||`about:blank`}),O(`td`,{class:q,children:ut(t.createdAt)}),O(`td`,{class:q,children:ft(t.createdAt)}),O(`td`,{class:J,children:O(`button`,{class:Y,onclick:`dashboard.killPage('${t.id}')`,children:`Close`})})]}))]}))})]})})}function vt({stats:e}){return k(`div`,{class:`stats-container`,children:[k(`div`,{class:G,children:[O(`h3`,{children:`Active Browsers`}),O(`div`,{class:`value`,children:e.totalBrowsers})]}),k(`div`,{class:G,children:[O(`h3`,{children:`Active Pages`}),O(`div`,{class:`value`,children:e.totalPages})]})]})}function yt({browsers:e,stats:t}){return k(`div`,{class:`dashboard-container`,children:[k(`div`,{class:`dashboard-header`,children:[k(`div`,{children:[O(`h1`,{children:`Playwright MCP Dashboard`}),O(`p`,{children:`Browser automation management (auto-refresh every 3 seconds)`})]}),k(`div`,{children:[O(`button`,{id:`refresh-btn`,class:`btn refresh-btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`}),O(`button`,{id:`kill-all-btn`,class:`btn btn-danger`,onclick:`dashboard.killAllBrowsers()`,children:`Kill All Browsers`})]})]}),O(vt,{stats:t}),O(_t,{browsers:e}),O(`script`,{children:ue(`
|
|
5
6
|
class DashboardManager {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.autoRefreshInterval = null;
|
|
@@ -39,7 +40,7 @@ class DashboardManager {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
updateStats(stats) {
|
|
42
|
-
const cards = document.querySelectorAll('.${
|
|
43
|
+
const cards = document.querySelectorAll('.${G} .value');
|
|
43
44
|
if (cards.length >= 2) {
|
|
44
45
|
cards[0].textContent = stats.totalBrowsers;
|
|
45
46
|
cards[1].textContent = stats.totalPages;
|
|
@@ -51,10 +52,10 @@ class DashboardManager {
|
|
|
51
52
|
if (!tbody) return;
|
|
52
53
|
|
|
53
54
|
if (browsers.length === 0) {
|
|
54
|
-
const container = tbody.closest('.${
|
|
55
|
+
const container = tbody.closest('.${W}');
|
|
55
56
|
if (container) {
|
|
56
57
|
container.innerHTML = \`
|
|
57
|
-
<div class="${
|
|
58
|
+
<div class="${gt}">
|
|
58
59
|
<h3>No Active Browsers</h3>
|
|
59
60
|
<p>Launch a browser using MCP tools to see it here.</p>
|
|
60
61
|
</div>
|
|
@@ -68,29 +69,29 @@ class DashboardManager {
|
|
|
68
69
|
browsers.forEach(browser => {
|
|
69
70
|
// Browser row
|
|
70
71
|
const browserRow = document.createElement('tr');
|
|
71
|
-
browserRow.className = '${
|
|
72
|
+
browserRow.className = '${pt}';
|
|
72
73
|
browserRow.innerHTML = \`
|
|
73
74
|
<td>\${browser.id}</td>
|
|
74
|
-
<td><span class="${
|
|
75
|
+
<td><span class="${K}">\${browser.pages.length} page\${browser.pages.length !== 1 ? 's' : ''}</span></td>
|
|
75
76
|
<td>\${browser.profileName || 'Default Profile'}</td>
|
|
76
|
-
<td class="${
|
|
77
|
-
<td class="${
|
|
78
|
-
<td class="${
|
|
77
|
+
<td class="${q}">\${this.formatTimestamp(browser.createdAt)}</td>
|
|
78
|
+
<td class="${q}">\${this.formatAge(browser.createdAt)}</td>
|
|
79
|
+
<td class="${J}"><button class="${Y}" onclick="dashboard.killBrowser('\${browser.id}')">Kill</button></td>
|
|
79
80
|
\`;
|
|
80
81
|
tbody.appendChild(browserRow);
|
|
81
82
|
|
|
82
83
|
// Page rows
|
|
83
84
|
browser.pages.forEach(page => {
|
|
84
85
|
const pageRow = document.createElement('tr');
|
|
85
|
-
pageRow.className = '${
|
|
86
|
+
pageRow.className = '${mt}';
|
|
86
87
|
const isActive = browser.currentPageId === page.id;
|
|
87
88
|
pageRow.innerHTML = \`
|
|
88
89
|
<td>\${page.id}\${isActive ? ' (active)' : ''}</td>
|
|
89
|
-
<td><span class="${
|
|
90
|
-
<td class="${
|
|
91
|
-
<td class="${
|
|
92
|
-
<td class="${
|
|
93
|
-
<td class="${
|
|
90
|
+
<td><span class="${K}">Open</span></td>
|
|
91
|
+
<td class="${ht}" title="\${this.escapeHtml(page.url)}">\${this.escapeHtml(page.title || page.url || 'about:blank')}</td>
|
|
92
|
+
<td class="${q}">\${this.formatTimestamp(page.createdAt)}</td>
|
|
93
|
+
<td class="${q}">\${this.formatAge(page.createdAt)}</td>
|
|
94
|
+
<td class="${J}"><button class="${Y}" onclick="dashboard.killPage('\${page.id}')">Close</button></td>
|
|
94
95
|
\`;
|
|
95
96
|
tbody.appendChild(pageRow);
|
|
96
97
|
});
|
|
@@ -186,7 +187,7 @@ const dashboard = new DashboardManager();
|
|
|
186
187
|
window.addEventListener('beforeunload', () => {
|
|
187
188
|
dashboard.stopAutoRefresh();
|
|
188
189
|
});
|
|
189
|
-
`)})]})}function
|
|
190
|
+
`)})]})}function bt({title:e,children:t}){return k(`html`,{lang:`en`,children:[k(`head`,{children:[O(`meta`,{charset:`UTF-8`}),O(`meta`,{name:`viewport`,content:`width=device-width, initial-scale=1.0`}),O(`title`,{children:e}),O(`style`,{children:ue(`
|
|
190
191
|
* {
|
|
191
192
|
margin: 0;
|
|
192
193
|
padding: 0;
|
|
@@ -427,8 +428,7 @@ window.addEventListener('beforeunload', () => {
|
|
|
427
428
|
margin-bottom: 0.5rem;
|
|
428
429
|
color: #999;
|
|
429
430
|
}
|
|
430
|
-
`)})]}),O(`body`,{children:t})]})}function Be(e){let t=new y;return t.get(`/`,async t=>{try{let n=e.get(s.BrowserService),r=e.get(s.PageRegistry),i=n.listBrowsers(),a=r.list(),o=i.map(e=>{let t=a.filter(t=>t.browserId===e.id);return{id:e.id,profileName:e.profileName,currentPageId:e.currentPageId,createdAt:e.createdAt,pages:t.map(e=>({id:e.id,url:e.url,title:e.title,createdAt:e.createdAt}))}}),c={totalBrowsers:i.length,totalPages:a.length};return t.html(O(ze,{title:`Playwright MCP Dashboard`,children:O(Re,{browsers:o,stats:c})}))}catch(e){return console.error(`Failed to render dashboard:`,e),t.html(O(ze,{title:`Playwright MCP Dashboard - Error`,children:k(`div`,{style:`padding: 2rem; text-align: center;`,children:[O(`h1`,{children:`Failed to load dashboard`}),O(`p`,{children:e instanceof Error?e.message:String(e)}),O(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),t}function Ve(e){let t=new y;t.use(`*`,b({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`DELETE`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),t.get(`/health`,t=>{try{let n=e.get(s.BrowserService).listBrowsers(),r={status:`healthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,timestamp:new Date().toISOString(),browsers:{count:n.length,instances:n.map(e=>({id:e.id,pageCount:e.pageIds.size,createdAt:e.createdAt.toISOString()}))}};return t.json(r)}catch(e){return t.json({status:`unhealthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,error:e instanceof Error?e.message:String(e)},503)}}),t.post(`/execute`,async t=>{try{let n=await t.req.json();if(!n.tool)return t.json({success:!1,error:`Missing "tool" field in request body`},400);let r=e.getAll(s.Tool).find(e=>e.getDefinition().name===n.tool);if(!r)return t.json({success:!1,error:`Tool "${n.tool}" not found`},404);let i=await r.execute(n.arguments||{}),a=(n.arguments||{}).pageId;if(a){let t=e.get(s.PageRegistry),n=e.get(s.BrowserService);t.touchPage(a);let r=t.get(a);r&&n.touchBrowser(r.browserId)}let o=i.isError?i.content[0]?.text:void 0;return t.json({success:!i.isError,result:i,error:o})}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/browsers`,t=>{try{let n=e.get(s.BrowserService).listBrowsers();return t.json({browsers:n.map(e=>({id:e.id,profileName:e.profileName,pageIds:Array.from(e.pageIds),currentPageId:e.currentPageId,createdAt:e.createdAt.toISOString()}))})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/tools`,t=>{try{let n=e.getAll(s.Tool);return t.json({tools:n.map(e=>e.getDefinition())})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}});let n=A(e);t.route(`/extension`,n);let r=Ae(e);t.route(`/api`,r);let i=Be(e);return t.route(`/`,i),t}function He(e,t,n){e.get(`/ws/extension/:browserId`,n(e=>{let n=e.req.param(`browserId`),r=null;return{onOpen(e,i){try{r=t.get(s.WebSocketHub).addConnection(i,n)}catch{i.close(1011,`Internal server error`)}},onMessage(e){if(r)try{let n=t.get(s.WebSocketHub),i=typeof e.data==`string`?e.data:e.data.toString();n.handleMessage(r,i)}catch{}},onClose(){if(r)try{t.get(s.WebSocketHub).removeConnection(r)}catch{}},onError(){if(r)try{t.get(s.WebSocketHub).removeConnection(r)}catch{}}}})),e.get(`/ws/stats`,e=>{try{let n=t.get(s.WebSocketHub).getStats();return e.json({totalConnections:n.totalConnections,browserCount:n.browserCount,connections:n.connections.map(e=>({id:e.id,browserId:e.browserId,connectedAt:e.connectedAt.toISOString()}))})}catch(t){return e.json({error:t instanceof Error?t.message:String(t)},500)}})}const Ue=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function We(e=process.cwd()){let t=T.resolve(e);for(;;){for(let e of Ue)if(C(T.join(t,e)))return t;let e=T.dirname(t);if(e===t)return process.cwd();t=e}}const Ge=new f(`http-serve`).description(`Start HTTP server for browser automation`).option(`-p, --port <port>`,`Port to listen on`,String(l)).option(`--host <host>`,`Host to bind`,a()).option(`--headless`,`Run browsers in headless mode by default`,!0).option(`--idle-timeout <minutes>`,`Idle timeout in minutes before closing browsers`,`30`).option(`--registry-dir <path>`,`Custom registry path or directory for service discovery`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking (deprecated)`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(e){let n=P(`httpServe`),r={port:F(this,`port`,e.port,n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),headless:F(this,`headless`,e.headless,n.headless),idleTimeout:F(this,`idleTimeout`,e.idleTimeout,n.idleTimeout===void 0?void 0:String(n.idleTimeout)),host:F(this,`host`,e.host,n.host,process.env.PLAYWRIGHT_HOST),registryDir:F(this,`registryDir`,e.registryDir,n.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),registryPath:F(this,`registryPath`,e.registryPath,n.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),pidsDir:F(this,`pidsDir`,e.pidsDir,n.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:F(this,`profilesDir`,e.profilesDir,n.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)};try{let e=Number.parseInt(r.port,10),n=`browse-tool-http`,a=process.env.NODE_ENV||`development`,o=We(process.cwd()),c=r.registryPath||r.registryDir||process.env.PLAYWRIGHT_REGISTRY_PATH;c&&(process.env.PLAYWRIGHT_REGISTRY_DIR=c,process.env.PORT_REGISTRY_PATH=c),process.env.PLAYWRIGHT_HOST=r.host,process.env.PLAYWRIGHT_PORT=r.port,r.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=r.pidsDir),r.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=r.profilesDir),console.log(`Starting HTTP server for browser automation...`),console.log(` Port: ${e}`),console.log(` Host: ${r.host}`),console.log(` Headless: ${r.headless}`),console.log(` Idle Timeout: ${r.idleTimeout} minutes`);let l=i(),u=Ve(l),{injectWebSocket:d,upgradeWebSocket:f}=re({app:u});He(u,l,f);let p=l.get(s.WebSocketHub),h=l.get(s.ExtensionTaskQueue),g=l.get(s.BrowserService),_=l.get(s.PageRegistry),v=l.get(s.IdleCleanupService);v.start(),p.setEventHandlers({onSessionRegister:(e,t)=>{if(t.type===`session:register`){let n=g.listBrowsers().find(e=>(e.mode===`extension`||e.mode===`vm`)&&!p.hasConnection(e.id)),r,i;if(n)r=n.id,i=n.currentPageId||Array.from(n.pageIds)[0]||``,p.reassignConnection(e.id,r),console.log(`[WebSocket] Extension connected to existing browser: ${r}, pageId: ${i}`);else{r=e.browserId,g.registerExtensionBrowserWithId(r),i=_.registerExtensionPage(r);let t=g.getBrowser(r);t&&(t.pageIds.add(i),t.currentPageId=i),console.log(`[WebSocket] Extension connected with new browser: ${r}, pageId: ${i}`)}let a=`session-${Date.now()}`;p.sendSessionAck(e,t.id??``,a,`extension`),p.broadcastPageCreated(r,i)}},onTaskResult:(e,t)=>{t.type===`task:result`&&h.submitResult({taskId:t.payload.taskId,success:t.payload.success,result:t.payload.result,error:t.payload.error})},onDisconnect:e=>{console.log(`[WebSocket] Extension disconnected: ${e.browserId}`)}});let y=new ne(process.env.PORT_REGISTRY_PATH);c?console.log(` Registry path: ${c}`):console.log(` Registry path: default (~/.port-registry/ports.json)`);let b=await y.reservePort({repositoryPath:o,serviceName:n,serviceType:`tool`,environment:a,preferredPort:e,pid:process.pid,host:r.host,force:!0,portRange:{min:e,max:e},metadata:{healthCheckUrl:`${t(r.host,e)}/health`,headless:r.headless,idleTimeout:r.idleTimeout}});if(!b.success||!b.record)throw Error(b.error||`Failed to reserve port ${e} in global registry`);let x=e,S;try{S=m({fetch:u.fetch,port:x,hostname:r.host})}catch(e){throw await y.releasePort({repositoryPath:o,serviceName:n,serviceType:`tool`,environment:a,pid:process.pid}),e}d(S);let C=t(r.host,x);console.log(`HTTP server listening on ${C}`),console.log(` Health check: ${C}/health`),console.log(` Execute tool: POST ${C}/execute`),console.log(`
|
|
431
|
-
Press Ctrl+C to stop the server`);let w=async e=>{console.log(`\n\n${e} received. Shutting down gracefully...`);try{v.stop(),S.close(),console.log(`Server closed`);try{await l.get(s.BrowserService).closeAll(),console.log(`All browsers closed`)}catch{}try{await y.releasePort({repositoryPath:o,serviceName:n,serviceType:`tool`,environment:a,pid:process.pid}),console.log(`Service deregistered`)}catch{}console.log(`Goodbye!`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>w(`SIGINT`)),process.on(`SIGTERM`,()=>w(`SIGTERM`))}catch(e){console.error(`Error starting HTTP server:`,e),process.exit(1)}}),Ke={browser_click:[`input`],browser_fill:[`input`],browser_type:[`input`],browser_select:[`input`],browser_hover:[`input`],browser_drag:[`input`],browser_press_key:[`input`],browser_upload_file:[`input`],browser_navigate:[`navigation`],browser_go_back:[`navigation`],browser_go_forward:[`navigation`],browser_reload:[`navigation`],browser_wait_for:[`navigation`],browser_snapshot:[`snapshot`],browser_screenshot:[`snapshot`],browser_pdf:[`snapshot`],browser_list_pages:[`page`],browser_new_page:[`page`],browser_select_page:[`page`],browser_close_page:[`page`],browser_resize_page:[`page`,`emulation`],browser_handle_dialog:[`dialog`],browser_list_network_requests:[`network`],browser_get_network_request:[`network`],browser_list_console_messages:[`console`],browser_evaluate_script:[`script`],browser_emulate:[`emulation`],browser_expect:[`testing`],browser_start_trace:[`tracing`],browser_stop_trace:[`tracing`],browser_list_profiles:[`profile`],browser_delete_profile:[`profile`],run_spec:[`spec`,`testing`],discover_specs:[`spec`,`testing`],browser_launch:[`browser`],browser_close:[`browser`],browser_run_code:[`code`,`script`]};function qe(e){let t=new Set;for(let[n,r]of Object.entries(Ke))r.some(t=>e.includes(t))&&t.add(n);return t}const Je=[`browser_launch`],Ye=[`browser_new_page`];function Xe(e){if(!e?.content?.[0]?.text)return null;try{let t=JSON.parse(e.content[0].text);return{browserId:t.browserId,pageId:t.pageId,mode:t.mode,url:t.url}}catch{return null}}async function Ze(e){await new Promise(t=>setTimeout(t,e))}async function Qe(e){let t=new AbortController,n=setTimeout(()=>t.abort(),3e3);try{let n=await fetch(`${e}/tools`,{signal:t.signal});if(!n.ok)throw Error(`HTTP error ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);if(!Array.isArray(r.tools))throw Error(`Invalid /tools response: missing tools array`);if(r.tools.length===0)throw Error(`HTTP server returned an empty tool list`);return r.tools}finally{clearTimeout(n)}}async function $e(e){let t;for(let n=1;n<=4;n+=1)try{return await Qe(e)}catch(e){t=e instanceof Error?e:Error(String(e)),n<4&&await Ze(150*n)}throw Error(`Failed to fetch tools after 4 attempts: ${t?.message??`Unknown error`}`,{cause:t})}function et(e){if(!e?.tags?.length&&!e?.exclude?.length)return null;let t=e.tags?.length?qe(e.tags):null,n=new Set(e.exclude??[]);if(!t)return n.size>0?n:null;for(let e of n)t.delete(e);return t}function tt(e){let{httpBaseUrl:t,sessionTracker:n,defaultMode:r,toolFilter:i}=e,a=i?.tags?.length?et(i):null,o=new Set(i?.exclude??[]),s=a!==null||o.size>0,c=new h({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),l={name:`browser_list_session`,description:`List all browsers and pages created in this MCP session. Use this to see what resources you have available.`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}},u=[];function d(e){return s?e.filter(e=>o.has(e.name)?!1:a===null?!0:a.has(e.name)):e}function f(e){return o.has(e)?!1:a===null?!0:a.has(e)}return c.setRequestHandler(v,async()=>{try{let e=await $e(t);u=e;let r=d(e);return n&&r.push(l),{tools:r}}catch(e){if(u.length>0){console.error(`Failed to refresh tools from HTTP server. Returning cached tools:`,e instanceof Error?e.message:String(e));let t=d(u);return n&&t.push(l),{tools:t}}let r=e instanceof Error?e.message:String(e);throw Error(`Failed to list tools from HTTP server at ${t}: ${r}`,{cause:e instanceof Error?e:void 0})}}),c.setRequestHandler(_,async e=>{let{name:i,arguments:a}=e.params;if(i!==`browser_list_session`&&!f(i))return{content:[{type:`text`,text:`Tool "${i}" is not available (filtered by --tags or --exclude)`}],isError:!0};if(i===`browser_list_session`&&n){let e=n.getSessionState();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}let o=a||{};i===`browser_launch`&&r&&(o={...o,mode:r});try{let e=await fetch(`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:i,arguments:o})});if(!e.ok){let t=await e.text();return{content:[{type:`text`,text:`HTTP error ${e.status}: ${t}`}],isError:!0}}let r=await e.json();if(!r.success)return{content:[{type:`text`,text:r.error||r.result?.content?.[0]?.text||`Unknown error from HTTP server`}],isError:!0};if(n&&r.result){if(Je.includes(i)){let e=Xe(r.result);e?.browserId&&(n.trackBrowser(e.browserId,e.mode||`playwright`),e.pageId&&n.trackPage(e.pageId,e.browserId,e.url))}else if(Ye.includes(i)){let e=Xe(r.result);e?.pageId&&e?.browserId&&n.trackPage(e.pageId,e.browserId,e.url)}}return r.result||{content:[{type:`text`,text:`No result returned`}]}}catch(e){return{content:[{type:`text`,text:`Failed to execute tool via HTTP server: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}),c}const Y=`stdio`,nt=new Set([Y]),rt=`chromium`,X=new Set([rt,`firefox`,`webkit`]),Z=new Set([`playwright`,`extension`,`vm`,`stealth`]),it=`SIGINT`,at=`SIGTERM`;var ot=class extends Error{code=`INVALID_TRANSPORT`;recovery=`Use --type ${Y} (currently the only supported transport)`;transportType;constructor(e,t){super(`Unknown transport type: ${e}`,t),this.name=`InvalidTransportError`,this.transportType=e}},st=class extends Error{code=`INVALID_BROWSER_TYPE`;recovery=`Use --browser ${[...X].join(`, `)}`;browserType;constructor(e,t){super(`Unknown browser type: ${e}`,t),this.name=`InvalidBrowserTypeError`,this.browserType=e}},ct=class extends Error{code=`INVALID_LAUNCH_MODE`;recovery=`Use --mode ${[...Z].join(`, `)}`;launchMode;constructor(e,t){super(`Unknown launch mode: ${e}`,t),this.name=`InvalidLaunchModeError`,this.launchMode=e}};function lt(e){let t=e.toLowerCase();if(!X.has(t))throw new st(e);return t}function ut(e){let t=e.toLowerCase();if(!Z.has(t))throw new ct(e);return t}function dt(e){return e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function ft(e){let t=e.tags?dt(e.tags):void 0,n=e.exclude?dt(e.exclude):void 0;if(!(!t?.length&&!n?.length))return{tags:t,exclude:n}}var pt=class extends Error{code=`BROWSER_CLEANUP_FAILED`;recovery=`Browsers may still be running; check the HTTP server or kill processes manually`;failedBrowserIds;httpBaseUrl;constructor(e,t,n){super(`Failed to close ${e.length} browser(s): ${e.join(`, `)}`,n),this.name=`BrowserCleanupError`,this.failedBrowserIds=e,this.httpBaseUrl=t}},mt=class extends Error{code=`SESSION_SHUTDOWN_FAILED`;recovery=`Check logs for cleanup and transport errors; processes may need manual cleanup`;signal;constructor(e,t){super(`Shutdown failed for signal ${e}`,t),this.name=`SessionShutdownError`,this.signal=e}},ht=class extends Error{code=`HTTP_SERVER_START_FAILED`;recovery=`Check if the HTTP server binary exists and the port is available`;constructor(e){super(`Failed to start HTTP server`,e),this.name=`HttpServerStartError`}},gt=class extends Error{code=`MCP_SERVER_BOOTSTRAP_FAILED`;recovery=`Check if all dependencies are installed and try again`;constructor(e){super(`Failed to start MCP server`,e),this.name=`McpServerBootstrapError`}};async function _t(e,t){let n=e.getBrowserIds(),r=[],i;for(let e of n)try{let n=await fetch(`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:`browser_close`,arguments:{browserId:e}})});if(!n.ok){let e=await n.text().catch(()=>``);throw Error(`HTTP ${n.status} from ${t}/execute: ${e}`)}console.error(` Closed browser: ${e}`)}catch(t){let n=t instanceof Error?t:Error(String(t));console.error(` Failed to close browser: ${e}`,n),r.push(e),i??=n}if(r.length>0)throw new pt(r,t,{cause:i});e.clear()}async function vt(e,t,n){await e.start();let r=!1,i=async i=>{if(r)return;r=!0,console.error(`\nReceived ${i}, shutting down gracefully...`);let a;try{let e=t.getSessionState();e.totalBrowsers>0&&(console.error(` Cleaning up ${e.totalBrowsers} browser(s) from this session...`),await _t(t,n))}catch(e){a=e instanceof Error?e:Error(String(e)),console.error(`Browser cleanup error:`,a)}finally{try{await e.stop()}catch(e){let t=e instanceof Error?e:Error(String(e));console.error(`Transport stop error:`,t),a??=t}if(a){let e=new mt(i,{cause:a});console.error(`Error during shutdown:`,e),process.exit(1)}process.exit(0)}};process.once(it,()=>i(it)),process.once(at,()=>i(at))}const yt=new f(`mcp-serve`).description(`Start Playwright MCP server for browser automation`).option(`-t, --type <type>`,`Transport type: ${Y}`,Y).option(`-b, --browser <browser>`,`Default browser type: ${[...X].join(`, `)}`,rt).option(`--headless`,`Run browsers in headless mode by default`,!1).option(`--no-headless`,`Run browsers in headed mode (visible window)`).option(`--host <host>`,`Default host for HTTP services`,a()).option(`-p, --profile <name>`,`Default profile name for browser sessions`).option(`-m, --mode <mode>`,`Default launch mode: ${[...Z].join(`, `)}`).option(`--tags <tags>`,`Comma-separated tags to filter tools (e.g. input,navigation,snapshot)`).option(`--exclude <tools>`,`Comma-separated tool names to exclude (applied after --tags)`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(n){let r=P(`mcpServe`),i={type:F(this,`type`,n.type,r.type),browser:F(this,`browser`,n.browser,r.browser),headless:F(this,`headless`,n.headless,r.headless),profile:F(this,`profile`,n.profile,r.profile),mode:F(this,`mode`,n.mode,r.mode),host:F(this,`host`,n.host,r.host,process.env.PLAYWRIGHT_HOST),tags:F(this,`tags`,n.tags,r.tags),exclude:F(this,`exclude`,n.exclude,r.exclude),registryPath:F(this,`registryPath`,n.registryPath,r.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),registryDir:F(this,`registryDir`,n.registryDir,r.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),pidsDir:F(this,`pidsDir`,n.pidsDir,r.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:F(this,`profilesDir`,n.profilesDir,r.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)},c=i.type.toLowerCase();try{if(!nt.has(c))throw new ot(c);let n=lt(i.browser),r=i.mode?ut(i.mode):void 0,l=ft(i);console.error(`Playwright MCP Server starting...`),console.error(` Transport: ${c}`),console.error(` Default browser: ${n}`),console.error(` Headless: ${i.headless}`),r&&console.error(` Default mode: ${r}`),i.profile&&console.error(` Profile: ${i.profile}`),l?.tags?.length&&console.error(` Tags: ${l.tags.join(`, `)}`),l?.exclude?.length&&console.error(` Exclude: ${l.exclude.join(`, `)}`);let f=i.registryPath||i.registryDir;f&&(process.env.PLAYWRIGHT_REGISTRY_DIR=f,process.env.PORT_REGISTRY_PATH=f,console.error(` Registry path: ${f}`)),i.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=i.pidsDir,console.error(` PIDs dir: ${i.pidsDir}`)),i.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=i.profilesDir,console.error(` Profiles dir: ${i.profilesDir}`)),i.host&&(process.env.PLAYWRIGHT_HOST=i.host),process.env.PLAYWRIGHT_PORT=d().toString();let p=await e().get(s.HttpServerManager).ensureRunning();if(!p.running)throw new ht;let m=t(a(),p.port),h=p.spawned?`spawned`:`reused`;console.error(` HTTP server: ${h} on port ${p.port}`);let g=new o;await vt(new u(tt({httpBaseUrl:m,sessionTracker:g,defaultMode:r,toolFilter:l})),g,m)}catch(e){(e instanceof ot||e instanceof st||e instanceof ct||e instanceof ht)&&(console.error(`Error [${e.code}]: ${e.message}`),console.error(`Recovery: ${e.recovery}`),process.exit(1));let t=new gt({cause:e instanceof Error?e:Error(String(e))});console.error(`Error [${t.code}]: ${t.message}`,t.cause),console.error(`Recovery: ${t.recovery}`),process.exit(1)}}),bt=new f(`status`).description(`Show status of HTTP server and diagnostics`).action(async()=>{try{let e=P(`httpServe`),n=process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH??e.registryPath??e.registryDir;n&&(process.env.PLAYWRIGHT_REGISTRY_DIR=n,process.env.PLAYWRIGHT_REGISTRY_PATH=n,process.env.PORT_REGISTRY_PATH=n),!process.env.PLAYWRIGHT_PIDS_DIR&&e.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=e.pidsDir),console.log(`browse-tool Status
|
|
432
|
-
`),console.log(`-`.repeat(50));let r=await
|
|
433
|
-
`)}}async function
|
|
434
|
-
//# sourceMappingURL=cli.mjs.map
|
|
431
|
+
`)})]}),O(`body`,{children:t})]})}function xt(e){let t=new S;return t.get(`/`,async t=>{try{let n=e.get(i.BrowserService),r=e.get(i.PageRegistry),a=n.listBrowsers(),o=r.list(),s=a.map(e=>{let t=o.filter(t=>t.browserId===e.id);return{id:e.id,profileName:e.profileName,currentPageId:e.currentPageId,createdAt:e.createdAt,pages:t.map(e=>({id:e.id,url:e.url,title:e.title,createdAt:e.createdAt}))}}),c={totalBrowsers:a.length,totalPages:o.length};return t.html(O(bt,{title:`Playwright MCP Dashboard`,children:O(yt,{browsers:s,stats:c})}))}catch(e){return console.error(`Failed to render dashboard:`,e),t.html(O(bt,{title:`Playwright MCP Dashboard - Error`,children:k(`div`,{style:`padding: 2rem; text-align: center;`,children:[O(`h1`,{children:`Failed to load dashboard`}),O(`p`,{children:e instanceof Error?e.message:String(e)}),O(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),t}function St(e){if(!e.isError)return;let t=e.content[0];return t?.type===`text`&&typeof t.text==`string`?t.text:`Unknown tool execution error`}function Ct(e){try{return e.get(i.TelemetryService)}catch{return new c}}function wt(e){let t=new S,n=e.get(i.PageRegistry),r=e.get(i.ExtensionTaskQueue),a=Ct(e),o=new ct(n,r,a);t.use(`*`,C({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`DELETE`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),t.get(`/health`,t=>{try{let n=e.get(i.BrowserService).listBrowsers(),r={status:`healthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,timestamp:new Date().toISOString(),browsers:{count:n.length,instances:n.map(e=>({id:e.id,pageCount:e.pageIds.size,createdAt:e.createdAt.toISOString()}))}};return t.json(r)}catch(e){return t.json({status:`unhealthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,error:e instanceof Error?e.message:String(e)},503)}}),t.post(`/execute`,async t=>{try{let r=await t.req.json();if(!r.tool)return t.json({success:!1,error:`Missing "tool" field in request body`},400);let o=typeof r.arguments?.pageId==`string`?r.arguments.pageId:void 0;return await a.runInSpan(`browse_tool.http.execute`,{attributes:{"http.method":`POST`,"http.route":`/execute`,"browse_tool.tool.name":r.tool,"browse_tool.page.id":o}},async o=>{let s=e.getAll(i.Tool).find(e=>e.getDefinition().name===r.tool);if(!s)return o?.setStatus({code:E.ERROR,message:`Tool "${r.tool}" not found`}),a.log(`warn`,`browse-tool HTTP execute rejected unknown tool`,{attributes:{"http.route":`/execute`,"browse_tool.tool.name":r.tool}}),t.json({success:!1,error:`Tool "${r.tool}" not found`},404);let c=await s.execute(r.arguments||{}),l=r.arguments||{},u=typeof l.pageId==`string`?l.pageId:void 0;if(u){let t=e.get(i.BrowserService);n.touchPage(u);let r=n.get(u);r&&t.touchBrowser(r.browserId)}let d=St(c);return d?(o?.setStatus({code:E.ERROR,message:d}),a.log(`error`,`browse-tool HTTP execute failed`,{attributes:{"http.route":`/execute`,"browse_tool.tool.name":r.tool,"browse_tool.page.id":u}})):a.log(`info`,`browse-tool HTTP execute succeeded`,{attributes:{"http.route":`/execute`,"browse_tool.tool.name":r.tool,"browse_tool.page.id":u}}),t.json({success:!c.isError,result:c,error:d})})}catch(e){return a.log(`error`,`browse-tool HTTP execute request crashed`,{attributes:{"http.route":`/execute`},exception:e}),t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/browsers`,t=>{try{let n=e.get(i.BrowserService).listBrowsers();return t.json({browsers:n.map(e=>({id:e.id,profileName:e.profileName,pageIds:Array.from(e.pageIds),currentPageId:e.currentPageId,createdAt:e.createdAt.toISOString()}))})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/tools`,t=>{try{let n=e.getAll(i.Tool);return t.json({tools:n.map(e=>e.getDefinition())})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/custom-tools`,async e=>{let t=e.req.query(`dir`);if(!t)return e.json({tools:[],error:`Missing "dir" query parameter`},400);try{let n=await o.listTools(t);return e.json({tools:n})}catch(t){return e.json({tools:[],error:t instanceof Error?t.message:String(t)},400)}}),t.post(`/custom-tools`,async t=>{let r=t.req.query(`dir`);if(!r)return t.json({success:!1,error:`Missing "dir" query parameter`},400);try{let s=await t.req.json();if(!s.tool)return t.json({success:!1,error:`Missing "tool" field in request body`},400);let c=typeof s.arguments?.pageId==`string`?s.arguments.pageId:void 0;return await a.runInSpan(`browse_tool.http.custom_tool.execute`,{attributes:{"http.method":`POST`,"http.route":`/custom-tools`,"browse_tool.tool.name":s.tool,"browse_tool.page.id":c,"browse_tool.custom_tools.directory":r}},async l=>{let u=await o.executeTool(r,s.tool,s.arguments||{});if(c){let t=e.get(i.BrowserService);n.touchPage(c);let r=n.get(c);r&&t.touchBrowser(r.browserId)}let d=St(u);return d?(l?.setStatus({code:E.ERROR,message:d}),a.log(`error`,`browse-tool custom tool execution failed`,{attributes:{"http.route":`/custom-tools`,"browse_tool.tool.name":s.tool,"browse_tool.page.id":c}})):a.log(`info`,`browse-tool custom tool execution succeeded`,{attributes:{"http.route":`/custom-tools`,"browse_tool.tool.name":s.tool,"browse_tool.page.id":c}}),t.json({success:!u.isError,result:u,error:d})})}catch(e){return a.log(`error`,`browse-tool custom tool request crashed`,{attributes:{"http.route":`/custom-tools`,"browse_tool.custom_tools.directory":r},exception:e}),t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}});let s=pe(e);t.route(`/extension`,s);let c=lt(e);t.route(`/api`,c);let l=xt(e);return t.route(`/`,l),t}function Tt(e,t,n){e.get(`/ws/extension/:browserId`,n(e=>{let n=e.req.param(`browserId`),r=null;return{onOpen(e,a){try{r=t.get(i.WebSocketHub).addConnection(a,n)}catch{a.close(1011,`Internal server error`)}},onMessage(e){if(r)try{let n=t.get(i.WebSocketHub),a=typeof e.data==`string`?e.data:e.data.toString();n.handleMessage(r,a)}catch{}},onClose(){if(r)try{t.get(i.WebSocketHub).removeConnection(r)}catch{}},onError(){if(r)try{t.get(i.WebSocketHub).removeConnection(r)}catch{}}}})),e.get(`/ws/stats`,e=>{try{let n=t.get(i.WebSocketHub).getStats();return e.json({totalConnections:n.totalConnections,browserCount:n.browserCount,connections:n.connections.map(e=>({id:e.id,browserId:e.browserId,connectedAt:e.connectedAt.toISOString()}))})}catch(t){return e.json({error:t instanceof Error?t.message:String(t)},500)}})}const Et=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function Dt(e=process.cwd()){let t=T.resolve(e);for(;;){for(let e of Et)if(w(T.join(t,e)))return t;let e=T.dirname(t);if(e===t)return process.cwd();t=e}}const Ot=new g(`http-serve`).description(`Start HTTP server for browser automation`).option(`-p, --port <port>`,`Port to listen on`,String(f)).option(`--host <host>`,`Host to bind`,s()).option(`--headless`,`Run browsers in headless mode by default`,!0).option(`--idle-timeout <minutes>`,`Idle timeout in minutes before closing browsers`,`30`).option(`--registry-dir <path>`,`Custom registry path or directory for service discovery`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking (deprecated)`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(e){let n=M(`httpServe`),r={port:N(this,`port`,e.port,n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),headless:N(this,`headless`,e.headless,n.headless),idleTimeout:N(this,`idleTimeout`,e.idleTimeout,n.idleTimeout===void 0?void 0:String(n.idleTimeout)),host:N(this,`host`,e.host,n.host,process.env.PLAYWRIGHT_HOST),registryDir:N(this,`registryDir`,e.registryDir,n.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),registryPath:N(this,`registryPath`,e.registryPath,n.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),pidsDir:N(this,`pidsDir`,e.pidsDir,n.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:N(this,`profilesDir`,e.profilesDir,n.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)};try{let e=Number.parseInt(r.port,10),n=`browse-tool-http`,a=process.env.NODE_ENV||`development`,s=Dt(process.cwd()),c=r.registryPath||r.registryDir||process.env.PLAYWRIGHT_REGISTRY_PATH;c&&(process.env.PLAYWRIGHT_REGISTRY_DIR=c,process.env.PORT_REGISTRY_PATH=c),process.env.PLAYWRIGHT_HOST=r.host,process.env.PLAYWRIGHT_PORT=r.port,r.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=r.pidsDir),r.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=r.profilesDir),console.log(`Starting HTTP server for browser automation...`),console.log(` Port: ${e}`),console.log(` Host: ${r.host}`),console.log(` Headless: ${r.headless}`),console.log(` Idle Timeout: ${r.idleTimeout} minutes`);let l=o(),u=wt(l),{injectWebSocket:d,upgradeWebSocket:f}=le({app:u});Tt(u,l,f);let p=l.get(i.WebSocketHub),m=l.get(i.ExtensionTaskQueue),h=l.get(i.BrowserService),g=l.get(i.PageRegistry),_=l.get(i.IdleCleanupService);_.start(),p.setEventHandlers({onSessionRegister:(e,t)=>{if(t.type===`session:register`){let n=h.listBrowsers().find(e=>(e.mode===`extension`||e.mode===`vm`)&&!p.hasConnection(e.id)),r,i;if(n)r=n.id,i=n.currentPageId||Array.from(n.pageIds)[0]||``,p.reassignConnection(e.id,r),console.log(`[WebSocket] Extension connected to existing browser: ${r}, pageId: ${i}`);else{r=e.browserId,h.registerExtensionBrowserWithId(r),i=g.registerExtensionPage(r);let t=h.getBrowser(r);t&&(t.pageIds.add(i),t.currentPageId=i),console.log(`[WebSocket] Extension connected with new browser: ${r}, pageId: ${i}`)}let a=`session-${Date.now()}`;p.sendSessionAck(e,t.id??``,a,`extension`),p.broadcastPageCreated(r,i)}},onTaskResult:(e,t)=>{t.type===`task:result`&&m.submitResult({taskId:t.payload.taskId,success:t.payload.success,result:t.payload.result,error:t.payload.error})},onDisconnect:e=>{console.log(`[WebSocket] Extension disconnected: ${e.browserId}`)}});let v=new ae(process.env.PORT_REGISTRY_PATH);c?console.log(` Registry path: ${c}`):console.log(` Registry path: default (~/.port-registry/ports.json)`);let y=await v.reservePort({repositoryPath:s,serviceName:n,serviceType:`tool`,environment:a,preferredPort:e,pid:process.pid,host:r.host,force:!0,portRange:{min:e,max:e},metadata:{healthCheckUrl:`${t(r.host,e)}/health`,headless:r.headless,idleTimeout:r.idleTimeout}});if(!y.success||!y.record)throw Error(y.error||`Failed to reserve port ${e} in global registry`);let b=e,x;try{x=ee({fetch:u.fetch,port:b,hostname:r.host})}catch(e){throw await v.releasePort({repositoryPath:s,serviceName:n,serviceType:`tool`,environment:a,pid:process.pid}),e}d(x);let S=t(r.host,b);console.log(`HTTP server listening on ${S}`),console.log(` Health check: ${S}/health`),console.log(` Execute tool: POST ${S}/execute`),console.log(`
|
|
432
|
+
Press Ctrl+C to stop the server`);let C=async e=>{console.log(`\n\n${e} received. Shutting down gracefully...`);try{_.stop(),x.close(),console.log(`Server closed`);try{await l.get(i.BrowserService).closeAll(),console.log(`All browsers closed`)}catch{}try{await v.releasePort({repositoryPath:s,serviceName:n,serviceType:`tool`,environment:a,pid:process.pid}),console.log(`Service deregistered`)}catch{}console.log(`Goodbye!`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>C(`SIGINT`)),process.on(`SIGTERM`,()=>C(`SIGTERM`))}catch(e){console.error(`Error starting HTTP server:`,e),process.exit(1)}}),kt={browser_click:[`input`],browser_fill:[`input`],browser_type:[`input`],browser_select:[`input`],browser_hover:[`input`],browser_drag:[`input`],browser_press_key:[`input`],browser_upload_file:[`input`],browser_navigate:[`navigation`],browser_go_back:[`navigation`],browser_go_forward:[`navigation`],browser_reload:[`navigation`],browser_wait_for:[`navigation`],browser_snapshot:[`snapshot`],browser_screenshot:[`snapshot`],browser_pdf:[`snapshot`],browser_list_pages:[`page`],browser_new_page:[`page`],browser_select_page:[`page`],browser_close_page:[`page`],browser_resize_page:[`page`,`emulation`],browser_handle_dialog:[`dialog`],browser_list_network_requests:[`network`],browser_get_network_request:[`network`],browser_list_console_messages:[`console`],browser_evaluate_script:[`script`],browser_emulate:[`emulation`],browser_expect:[`testing`],browser_start_trace:[`tracing`],browser_stop_trace:[`tracing`],browser_list_profiles:[`profile`],browser_delete_profile:[`profile`],run_spec:[`spec`,`testing`],discover_specs:[`spec`,`testing`],browser_launch:[`browser`],browser_close:[`browser`],browser_run_code:[`code`,`script`]};function At(e){let t=new Set;for(let[n,r]of Object.entries(kt))r.some(t=>e.includes(t))&&t.add(n);return t}const jt=3e3,Mt=[`browser_launch`],Nt=[`browser_new_page`];function Pt(e){if(!e?.content?.[0]?.text)return null;try{let t=JSON.parse(e.content[0].text);return{browserId:t.browserId,pageId:t.pageId,mode:t.mode,url:t.url}}catch{return null}}async function Ft(e){await new Promise(t=>setTimeout(t,e))}async function It(e){let t=new AbortController,n=setTimeout(()=>t.abort(),jt);try{let n=await fetch(`${e}/tools`,{signal:t.signal});if(!n.ok)throw Error(`HTTP error ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);if(!Array.isArray(r.tools))throw Error(`Invalid /tools response: missing tools array`);if(r.tools.length===0)throw Error(`HTTP server returned an empty tool list`);return r.tools}finally{clearTimeout(n)}}function Lt(e,t){let n=new URL(`/custom-tools`,e);return n.searchParams.set(`dir`,t),n.toString()}async function Rt(e,t){let n=new AbortController,r=setTimeout(()=>n.abort(),jt);try{let r=await fetch(Lt(e,t),{signal:n.signal});if(!r.ok)throw Error(`HTTP error ${r.status}: ${r.statusText}`);let i=await r.json();if(i.error)throw Error(i.error);if(!Array.isArray(i.tools))throw Error(`Invalid /custom-tools response: missing tools array`);return i.tools}finally{clearTimeout(r)}}async function zt(e){let t;for(let n=1;n<=4;n+=1)try{return await It(e)}catch(e){t=e instanceof Error?e:Error(String(e)),n<4&&await Ft(150*n)}throw Error(`Failed to fetch tools after 4 attempts: ${t?.message??`Unknown error`}`,{cause:t})}async function Bt(e,t){let n;for(let r=1;r<=4;r+=1)try{return await Rt(e,t)}catch(e){n=e instanceof Error?e:Error(String(e)),r<4&&await Ft(150*r)}throw Error(`Failed to fetch custom tools after 4 attempts: ${n?.message??`Unknown error`}`,{cause:n})}function Vt(e){if(!e?.tags?.length&&!e?.exclude?.length)return null;let t=e.tags?.length?At(e.tags):null,n=new Set(e.exclude??[]);if(!t)return n.size>0?n:null;for(let e of n)t.delete(e);return t}function Ht(e){let{httpBaseUrl:t,sessionTracker:n,defaultMode:r,toolFilter:i,customToolsDir:a}=e,o=i?.tags?.length?Vt(i):null,s=new Set(i?.exclude??[]),c=o!==null||s.size>0,l=new v({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),u={name:`browser_list_session`,description:`List all browsers and pages created in this MCP session. Use this to see what resources you have available.`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}},d=[],f=[];function p(e){return c?e.filter(e=>s.has(e.name)?!1:o===null?!0:o.has(e.name)):e}function m(e){return e.filter(e=>!s.has(e.name))}function h(e){return{name:e.name,description:e.description,inputSchema:e.inputSchema,annotations:e.capabilities}}function g(e){return s.has(e)?!1:o===null?!0:o.has(e)}return l.setRequestHandler(x,async()=>{try{let e=await zt(t);d=e;let r=a?await Bt(t,a):[];f=r;let i=[...p(e),...m(r).map(e=>h(e))];return n&&i.push(u),{tools:i}}catch(e){if(d.length>0||f.length>0){console.error(`Failed to refresh tools from HTTP server. Returning cached tools:`,e instanceof Error?e.message:String(e));let t=[...p(d),...m(f).map(e=>h(e))];return n&&t.push(u),{tools:t}}let r=e instanceof Error?e.message:String(e);throw Error(`Failed to list tools from HTTP server at ${t}: ${r}`,{cause:e})}}),l.setRequestHandler(b,async e=>{let{name:i,arguments:o}=e.params,s=new Set(f.map(e=>e.name));if(i!==`browser_list_session`&&!g(i))return{content:[{type:`text`,text:`Tool "${i}" is not available (filtered by --tags or --exclude)`}],isError:!0};if(i===`browser_list_session`&&n){let e=n.getSessionState();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}let c=o||{};i===`browser_launch`&&r&&(c={...c,mode:r});try{a&&!s.has(i)&&(f=await Bt(t,a),s=new Set(f.map(e=>e.name)));let e=s.has(i),r=await fetch(e&&a?Lt(t,a):`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:i,arguments:c})});if(!r.ok){let e=await r.text();return{content:[{type:`text`,text:`HTTP error ${r.status}: ${e}`}],isError:!0}}let o=await r.json();if(!o.success)return{content:[{type:`text`,text:o.error||o.result?.content?.[0]?.text||`Unknown error from HTTP server`}],isError:!0};if(n&&o.result){if(Mt.includes(i)){let e=Pt(o.result);e?.browserId&&(n.trackBrowser(e.browserId,e.mode||`playwright`),e.pageId&&n.trackPage(e.pageId,e.browserId,e.url))}else if(Nt.includes(i)){let e=Pt(o.result);e?.pageId&&e?.browserId&&n.trackPage(e.pageId,e.browserId,e.url)}}return o.result||{content:[{type:`text`,text:`No result returned`}]}}catch(e){return{content:[{type:`text`,text:`Failed to execute tool via HTTP server: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}),l}const X=`stdio`,Ut=new Set([X]),Wt=`chromium`,Z=new Set([Wt,`firefox`,`webkit`]),Q=new Set([`playwright`,`extension`,`vm`,`stealth`]),Gt=`SIGINT`,Kt=`SIGTERM`;var qt=class extends Error{code=`INVALID_TRANSPORT`;recovery=`Use --type ${X} (currently the only supported transport)`;transportType;constructor(e,t){super(`Unknown transport type: ${e}`,t),this.name=`InvalidTransportError`,this.transportType=e}},Jt=class extends Error{code=`INVALID_BROWSER_TYPE`;recovery=`Use --browser ${[...Z].join(`, `)}`;browserType;constructor(e,t){super(`Unknown browser type: ${e}`,t),this.name=`InvalidBrowserTypeError`,this.browserType=e}},Yt=class extends Error{code=`INVALID_LAUNCH_MODE`;recovery=`Use --mode ${[...Q].join(`, `)}`;launchMode;constructor(e,t){super(`Unknown launch mode: ${e}`,t),this.name=`InvalidLaunchModeError`,this.launchMode=e}};function Xt(e){let t=e.toLowerCase();if(!Z.has(t))throw new Jt(e);return t}function Zt(e){let t=e.toLowerCase();if(!Q.has(t))throw new Yt(e);return t}function Qt(e){return e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function $t(e){let t=e.tags?Qt(e.tags):void 0,n=e.exclude?Qt(e.exclude):void 0;if(!(!t?.length&&!n?.length))return{tags:t,exclude:n}}var en=class extends Error{code=`BROWSER_CLEANUP_FAILED`;recovery=`Browsers may still be running; check the HTTP server or kill processes manually`;failedBrowserIds;httpBaseUrl;constructor(e,t,n){super(`Failed to close ${e.length} browser(s): ${e.join(`, `)}`,n),this.name=`BrowserCleanupError`,this.failedBrowserIds=e,this.httpBaseUrl=t}},tn=class extends Error{code=`SESSION_SHUTDOWN_FAILED`;recovery=`Check logs for cleanup and transport errors; processes may need manual cleanup`;signal;constructor(e,t){super(`Shutdown failed for signal ${e}`,t),this.name=`SessionShutdownError`,this.signal=e}},nn=class extends Error{code=`HTTP_SERVER_START_FAILED`;recovery=`Check if the HTTP server binary exists and the port is available`;constructor(e){super(`Failed to start HTTP server`,e),this.name=`HttpServerStartError`}},rn=class extends Error{code=`MCP_SERVER_BOOTSTRAP_FAILED`;recovery=`Check if all dependencies are installed and try again`;constructor(e){super(`Failed to start MCP server`,e),this.name=`McpServerBootstrapError`}};async function an(e,t){let n=e.getBrowserIds(),r=[],i;for(let e of n)try{let n=await fetch(`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:`browser_close`,arguments:{browserId:e}})});if(!n.ok){let e=await n.text().catch(()=>``);throw Error(`HTTP ${n.status} from ${t}/execute: ${e}`)}console.error(` Closed browser: ${e}`)}catch(t){let n=t instanceof Error?t:Error(String(t));console.error(` Failed to close browser: ${e}`,n),r.push(e),i??=n}if(r.length>0)throw new en(r,t,{cause:i});e.clear()}async function on(e,t,n){await e.start();let r=!1,i=async i=>{if(r)return;r=!0,console.error(`\nReceived ${i}, shutting down gracefully...`);let a;try{let e=t.getSessionState();e.totalBrowsers>0&&(console.error(` Cleaning up ${e.totalBrowsers} browser(s) from this session...`),await an(t,n))}catch(e){a=e instanceof Error?e:Error(String(e)),console.error(`Browser cleanup error:`,a)}finally{try{await e.stop()}catch(e){let t=e instanceof Error?e:Error(String(e));console.error(`Transport stop error:`,t),a??=t}if(a){let e=new tn(i,{cause:a});console.error(`Error during shutdown:`,e),process.exit(1)}process.exit(0)}};process.once(Gt,()=>i(Gt)),process.once(Kt,()=>i(Kt))}const sn=new g(`mcp-serve`).description(`Start Playwright MCP server for browser automation`).option(`-t, --type <type>`,`Transport type: ${X}`,X).option(`-b, --browser <browser>`,`Default browser type: ${[...Z].join(`, `)}`,Wt).option(`--headless`,`Run browsers in headless mode by default`,!1).option(`--no-headless`,`Run browsers in headed mode (visible window)`).option(`--host <host>`,`Default host for HTTP services`,s()).option(`-p, --profile <name>`,`Default profile name for browser sessions`).option(`-m, --mode <mode>`,`Default launch mode: ${[...Q].join(`, `)}`).option(`--tags <tags>`,`Comma-separated tags to filter tools (e.g. input,navigation,snapshot)`).option(`--exclude <tools>`,`Comma-separated tool names to exclude (applied after --tags)`).option(`--custom-tools <path>`,`Path to a folder containing tools.yaml and custom tool scripts`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(n){let r=M(`mcpServe`),a={type:N(this,`type`,n.type,r.type),browser:N(this,`browser`,n.browser,r.browser),headless:N(this,`headless`,n.headless,r.headless),profile:N(this,`profile`,n.profile,r.profile),mode:N(this,`mode`,n.mode,r.mode),host:N(this,`host`,n.host,r.host,process.env.PLAYWRIGHT_HOST),tags:N(this,`tags`,n.tags,r.tags),exclude:N(this,`exclude`,n.exclude,r.exclude),customTools:N(this,`customTools`,n.customTools,r.customTools),registryPath:N(this,`registryPath`,n.registryPath,r.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),registryDir:N(this,`registryDir`,n.registryDir,r.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),pidsDir:N(this,`pidsDir`,n.pidsDir,r.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:N(this,`profilesDir`,n.profilesDir,r.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)},o=a.type.toLowerCase();try{if(!Ut.has(o))throw new qt(o);let n=Xt(a.browser),r=a.mode?Zt(a.mode):void 0,c=$t(a),u=a.customTools?T.resolve(a.customTools):void 0;console.error(`Playwright MCP Server starting...`),console.error(` Transport: ${o}`),console.error(` Default browser: ${n}`),console.error(` Headless: ${a.headless}`),r&&console.error(` Default mode: ${r}`),a.profile&&console.error(` Profile: ${a.profile}`),c?.tags?.length&&console.error(` Tags: ${c.tags.join(`, `)}`),c?.exclude?.length&&console.error(` Exclude: ${c.exclude.join(`, `)}`),u&&console.error(` Custom tools: ${u}`);let d=a.registryPath||a.registryDir;d&&(process.env.PLAYWRIGHT_REGISTRY_DIR=d,process.env.PORT_REGISTRY_PATH=d,console.error(` Registry path: ${d}`)),a.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=a.pidsDir,console.error(` PIDs dir: ${a.pidsDir}`)),a.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=a.profilesDir,console.error(` Profiles dir: ${a.profilesDir}`)),a.host&&(process.env.PLAYWRIGHT_HOST=a.host),process.env.PLAYWRIGHT_PORT=m().toString();let f=await e().get(i.HttpServerManager).ensureRunning();if(!f.running)throw new nn;let h=t(s(),f.port),g=f.spawned?`spawned`:`reused`;console.error(` HTTP server: ${g} on port ${f.port}`);let _=new l;await on(new p(Ht({httpBaseUrl:h,sessionTracker:_,defaultMode:r,toolFilter:c,customToolsDir:u})),_,h)}catch(e){(e instanceof qt||e instanceof Jt||e instanceof Yt||e instanceof nn)&&(console.error(`Error [${e.code}]: ${e.message}`),console.error(`Recovery: ${e.recovery}`),process.exit(1));let t=new rn({cause:e instanceof Error?e:Error(String(e))});console.error(`Error [${t.code}]: ${t.message}`,t.cause),console.error(`Recovery: ${t.recovery}`),process.exit(1)}}),cn=new g(`status`).description(`Show status of HTTP server and diagnostics`).action(async()=>{try{let e=M(`httpServe`),n=process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH??e.registryPath??e.registryDir;n&&(process.env.PLAYWRIGHT_REGISTRY_DIR=n,process.env.PLAYWRIGHT_REGISTRY_PATH=n,process.env.PORT_REGISTRY_PATH=n),!process.env.PLAYWRIGHT_PIDS_DIR&&e.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=e.pidsDir),console.log(`browse-tool Status
|
|
433
|
+
`),console.log(`-`.repeat(50));let r=await d().get(i.HttpServerManager).getStatus();if(r.running){let n=process.env.PLAYWRIGHT_HOST??e.host??s();console.log(`HTTP Server: Running`),console.log(` PID: ${r.pid}`),console.log(` Port: ${r.port}`),console.log(` Health: ${t(n,r.port)}/health`),r.browserCount!==void 0&&console.log(` Browsers: ${r.browserCount}`)}else console.log(`HTTP Server: Not Running`),r.error&&console.log(` Error: ${r.error}`);console.log(``),console.log(`-`.repeat(50)),process.exit(0)}catch(e){console.error(`Error getting status:`,e),process.exit(1)}}),ln=new g(`stop`).description(`Stop HTTP server and clean up registry/PID files`).action(async()=>{try{let e=M(`httpServe`),t=process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH??e.registryPath??e.registryDir;t&&(process.env.PLAYWRIGHT_REGISTRY_DIR=t,process.env.PLAYWRIGHT_REGISTRY_PATH=t,process.env.PORT_REGISTRY_PATH=t),!process.env.PLAYWRIGHT_PIDS_DIR&&e.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=e.pidsDir),console.log(`Stopping browse-tool services...`),await d().get(i.HttpServerManager).stop()?(console.log(`HTTP server stopped`),console.log(`Registry and PID files cleaned up`)):console.log(`No HTTP server was running`),console.log(`Done!`),process.exit(0)}catch(e){console.error(`Error stopping services:`,e),process.exit(1)}}),un=new Set([`--config`]);function dn(e){for(let t=0;t<e.length;t+=1){let n=e[t];if(un.has(n)){t+=1;continue}if(![...un].some(e=>n.startsWith(`${e}=`))&&!n.startsWith(`-`))return n}}function fn(e,t=!1){if(t)return!1;let n=dn(e);return n?n===`tools`?!0:n===`help`&&e.includes(`tools`):!1}function pn(e){return e.replace(/_/g,`-`)}function $(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).toLowerCase()}function mn(e){let t=new Set;e.type&&t.add(e.type);for(let n of e.oneOf??[])n.type&&t.add(n.type);return t}function hn(e,t){let n=mn(t);if(t.type===`number`||t.type===`integer`){let t=Number(e);if(Number.isNaN(t))throw Error(`Invalid number: ${e}`);return t}if(t.type===`boolean`){if(e===`true`||e===`1`)return!0;if(e===`false`||e===`0`)return!1;throw Error(`Invalid boolean: ${e}`)}if(n.has(`array`)||n.has(`object`))try{return JSON.parse(e)}catch{if(t.type===`array`||t.type===`object`)throw Error(`Invalid JSON: ${e}`)}if(n.has(`number`)||n.has(`integer`)){let t=Number(e);if(!Number.isNaN(t)&&e.trim()!==``)return t}return e}function gn(e,t){let n=$(e);return t.type===`boolean`?`--${n}`:`--${n} <value>`}function _n(e){let{definition:t,execute:n}=e,r=new g(pn(t.name));r.description(t.description);let i=t.inputSchema,a=i.properties||{},o=new Set(i.required||[]);for(let[e,t]of Object.entries(a)){let n=t,i=gn(e,n),a=n.description||``;n.enum&&n.enum.length>0&&(a+=` (choices: ${n.enum.join(`, `)})`),n.default!==void 0&&(a+=` (default: ${JSON.stringify(n.default)})`),o.has(e)&&(a+=` [required]`),n.type===`boolean`?n.default===!0?r.option(`--no-${$(e)}`,`Disable ${a}`):r.option(i,a,n.default):n.enum&&n.enum.length>0?r.addOption(new _(i,a).choices(n.enum)):r.option(i,a)}return r.action(async function(){let e=this.optsWithGlobals(),r=M(`tools`),i=N(this,`format`,e.format??`json`,r.format),s=N(this,`color`,e.color??!0,r.color),c=N(this,`port`,String(e.port??f),r.port===void 0?void 0:String(r.port),process.env.PLAYWRIGHT_PORT),l={format:i,color:s},u=Ae(t.name);try{let r={};for(let[t,n]of Object.entries(a)){let i=n,a=$(t).replace(/-([a-z])/g,(e,t)=>t.toUpperCase()),o=this.getOptionValueSourceWithGlobals(a),s=e[a];i.type===`boolean`&&i.default===!0&&(s=e[a]),(o===void 0||o===`default`||o===`implied`)&&u[t]!==void 0&&(s=u[t]),s!==void 0&&(typeof s==`string`&&i.type!==`string`?r[t]=hn(s,i):r[t]=s)}for(let e of o)if(r[e]===void 0){let t=$(e);console.error(L(`Missing required option: --${t}`,l.color)),process.exit(1)}let i=Number.parseInt(c,10),s=P({port:i}),d=n?await n(r,{command:this,formatterOptions:l,port:i}):await s.execute(t.name,r),f=I(d,l);f&&console.log(f),d.isError&&process.exit(1)}catch(e){console.error(L(e instanceof Error?e:String(e),l.color)),process.exit(1)}}),r}function vn(e){return e.map(_n)}const yn={name:`browser_list_session`,description:`List browsers and pages currently tracked by the browse-tool HTTP server.`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}};function bn(){return new g(`tools`).description(`Execute browser automation tools`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(f))}function xn(e){return{browserCount:e.length,browsers:e.map(e=>({browserId:e.id,profileName:e.profileName,currentPageId:e.currentPageId,pageIds:e.pageIds,createdAt:e.createdAt}))}}function Sn(){return{definition:yn,execute:async(e,t)=>{let n=await P({port:t.port}).listBrowsers();return{content:[{type:`text`,text:JSON.stringify(xn(n),null,2)}]}}}}function Cn(e){return[...e.map(e=>({definition:e})),Sn()]}async function wn(e,t){let n=M(`tools`),r=Number(t?.port??process.env.PLAYWRIGHT_PORT??n.port??f);try{let t=vn(Cn(await P({port:r}).listTools()));for(let n of t)e.addCommand(n)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`${L(`Failed to load tool commands: ${t}`,!0)}\n`),process.stderr.write(`Make sure the HTTP server is running: browse-tool http-serve
|
|
434
|
+
`)}}async function Tn(){let e=ke(process.argv.slice(2)),t=process.env.PLAYWRIGHT_PORT??e.config.commands.tools?.port??e.config.commands.exec?.port??f,n=new g;n.name(`browse-tool`).description(`MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support`).option(`--config <path>`,`Path to browse-tool config file or config directory`).version(fe),n.addCommand(sn),n.addCommand(Ot),n.addCommand(ge),n.addCommand(ln),n.addCommand(cn),n.addCommand(Ue),n.addCommand(Ve),n.addCommand(He);let r=bn();fn(process.argv.slice(2),process.env.PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS===`1`)&&await wn(r,{port:Number(t)}),n.addCommand(r),await n.parseAsync(process.argv)}Tn().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});export{};
|