@agimon-ai/browse-tool 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +21 -21
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{playwright-test-CGgt4gB5.cjs → playwright-test-Gy2QgWt3.cjs} +2 -2
- package/dist/{playwright-test-CGgt4gB5.cjs.map → playwright-test-Gy2QgWt3.cjs.map} +1 -1
- package/dist/streamable-http-BF_kFDOr.mjs +13 -0
- package/dist/streamable-http-BF_kFDOr.mjs.map +1 -0
- package/dist/streamable-http-DEnR3pMh.cjs +13 -0
- package/dist/streamable-http-DEnR3pMh.cjs.map +1 -0
- package/dist/stubs/playwright-test.cjs +1 -1
- package/package.json +3 -2
- package/dist/streamable-http-BFbkLZAL.mjs +0 -14
- package/dist/streamable-http-BFbkLZAL.mjs.map +0 -1
- package/dist/streamable-http-CkhxOp5e.cjs +0 -15
- package/dist/streamable-http-CkhxOp5e.cjs.map +0 -1
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.cjs","names":["Hono","telemetry: ITelemetryService","container","PLAYWRIGHT_TYPES","TelemetryService","resolveBrowserTelemetryConfig","result: ExtensionTaskResult","response: StatusResponse","ContainerModule","PLAYWRIGHT_TYPES","ExtensionTaskQueue","ExtensionToolDelegator","Server","toolDefinitions: ToolDefinition[]","ListToolsRequestSchema","CallToolRequestSchema","Command","container","Container","Hono","StdioServerTransport","DEFAULT_CONFIG: BrowseToolConfig","z","runtimeContext: CliRuntimeContext","path","createMcpContainer","PLAYWRIGHT_TYPES","lines: string[]","Command","DEFAULT_MCP_PORT","args: Record<string, unknown>","Command","buildDockerChromeForTestingImage","resolveChromeForTestingArchivePlatform","Command","DEFAULT_MCP_PORT","formatterOptions: FormatterOptions","args: Record<string, unknown>","z","attributes: Attributes","result: unknown[]","objectValue: Record<string, unknown>","result: Record<string, unknown>","pageRegistry: IPageRegistry","extensionTaskQueue?: ExtensionTaskQueue","telemetry: ITelemetryService","TelemetryService","browserService?: IBrowserService","ExtensionPageProxy","pageId","delegatedPayload: { url?: string; title?: string; tabId?: number }","pageEntry","browser","baseAttributes: Record<string, unknown>","path","SpanStatusCode","Hono","container","PLAYWRIGHT_TYPES","browsers: BrowserResponse[]","stats: StatsResponse","styles.tableContainer","styles.emptyState","styles.table","styles.browserRow","styles.statusActive","styles.timestampCell","styles.actionsCell","styles.killButton","styles.pageRow","styles.urlCell","styles.statsContainer","styles.statCard","styles.container","styles.header","styles.button","styles.refreshButton","styles.buttonDanger","styles.statCard","styles.tableContainer","styles.emptyState","styles.browserRow","styles.statusActive","styles.timestampCell","styles.actionsCell","styles.killButton","styles.pageRow","styles.urlCell","Hono","container","PLAYWRIGHT_TYPES","browsers: BrowserData[]","container","PLAYWRIGHT_TYPES","TelemetryService","Hono","response: HealthResponse","SpanStatusCode","browserService","connectionId: string | null","container","PLAYWRIGHT_TYPES","path","Command","DEFAULT_MCP_PORT","getPlaywrightHost","resolvedOptions: HttpServeOptions","BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR","container","createHttpContainer","PLAYWRIGHT_TYPES","browserId: string","pageId: string","PortRegistryService","buildPlaywrightBaseUrl","TOOL_TAGS: Record<string, ToolTag[]>","lastError: Error | undefined","Server","sessionToolDefinition: ToolDefinition","cachedTools: ToolDefinition[]","cachedCustomTools: CustomToolDescriptor[]","ListToolsRequestSchema","CallToolRequestSchema","failedIds: string[]","firstCause: Error | undefined","cleanupError: Error | undefined","Command","getPlaywrightHost","resolvedOptions: McpServeOptions","BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR","path","getPlaywrightPort","createMcpContainer","PLAYWRIGHT_TYPES","buildPlaywrightBaseUrl","McpSessionTracker","StdioTransportHandler","StreamableHttpTransportHandler","Command","createContainer","PLAYWRIGHT_TYPES","getPlaywrightHost","buildPlaywrightBaseUrl","Command","createContainer","PLAYWRIGHT_TYPES","Command","Option","DEFAULT_MCP_PORT","formatterOptions: FormatterOptions","args: Record<string, unknown>","SESSION_TOOL_DEFINITION: ToolDefinition","Command","DEFAULT_MCP_PORT","DEFAULT_MCP_PORT","Command","packageJson.version"],"sources":["../package.json","../src/server/extension-routes.ts","../src/commands/chrome-serve.ts","../src/config.ts","../src/utils/httpClient.ts","../src/utils/outputFormatter.ts","../src/commands/custom-tools.ts","../src/commands/docker-build-cft.ts","../src/commands/exec.ts","../src/services/CustomToolService.ts","../src/server/dashboard/routes/api.ts","../src/server/dashboard/utils/formatters.ts","../src/server/dashboard/components/styles.ts","../src/server/dashboard/components/BrowserTable.tsx","../src/server/dashboard/components/StatsHeader.tsx","../src/server/dashboard/components/Dashboard.tsx","../src/server/dashboard/components/Layout.tsx","../src/server/dashboard/routes/dashboard.tsx","../src/server/http.ts","../src/server/websocket-routes.ts","../src/commands/http-serve.ts","../src/constants/tool-tags.ts","../src/server/proxy.ts","../src/commands/mcp-serve.ts","../src/commands/status.ts","../src/commands/stop.ts","../src/utils/cliArgs.ts","../src/utils/commandBuilder.ts","../src/utils/toolCommands.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@agimon-ai/browse-tool\",\n \"description\": \"MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support\",\n \"version\": \"0.2.6\",\n \"license\": \"BUSL-1.1\",\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"playwright\",\n \"browser-automation\",\n \"typescript\"\n ],\n \"bin\": {\n \"browse-tool\": \"./dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"types\": \"./dist/index.d.cts\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"dev\": \"tsx src/cli.ts mcp-serve\",\n \"build\": \"tsdown && pnpm build:extension\",\n \"build:extension\": \"vite build --config vite.config.extension.ts\",\n \"build:vm-image\": \"node ./scripts/build-vm-image.mjs\",\n \"dev:extension\": \"vite build --config vite.config.extension.ts --watch --mode development\",\n \"test\": \"vitest --run\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src\"\n },\n \"dependencies\": {\n \"@agimon-ai/foundation-port-registry\": \"workspace:*\",\n \"@hono/node-server\": \"1.19.9\",\n \"@hono/node-ws\": \"^1.3.0\",\n \"@modelcontextprotocol/sdk\": \"1.19.1\",\n \"@opentelemetry/api\": \"1.9.0\",\n \"@opentelemetry/api-logs\": \"0.213.0\",\n \"@opentelemetry/exporter-logs-otlp-http\": \"0.213.0\",\n \"@opentelemetry/exporter-trace-otlp-http\": \"0.213.0\",\n \"@opentelemetry/resources\": \"2.6.0\",\n \"@opentelemetry/sdk-logs\": \"0.213.0\",\n \"@opentelemetry/sdk-trace-node\": \"2.6.0\",\n \"@opentelemetry/semantic-conventions\": \"1.40.0\",\n \"@playwright/test\": \"1.57.0\",\n \"chalk\": \"5.6.2\",\n \"commander\": \"14.0.1\",\n \"hono\": \"4.11.4\",\n \"inversify\": \"7.0.1\",\n \"liquidjs\": \"^10.24.0\",\n \"playwright\": \"1.57.0\",\n \"reflect-metadata\": \"0.2.2\",\n \"ws\": \"8.18.0\",\n \"zod\": \"^4.3.6\"\n },\n \"devDependencies\": {\n \"@types/chrome\": \"0.0.322\",\n \"@types/node\": \"^22.0.0\",\n \"@types/ws\": \"8.5.13\",\n \"tsdown\": \"0.16.1\",\n \"typescript\": \"5.9.3\",\n \"unplugin-raw\": \"^0.6.4\",\n \"vite\": \"7.0.5\",\n \"vite-plugin-static-copy\": \"^3.1.5\",\n \"vitest\": \"4.0.18\"\n },\n \"type\": \"module\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./cli\": {\n \"import\": \"./dist/cli.mjs\",\n \"require\": \"./dist/cli.cjs\"\n },\n \"./stubs/playwright-test\": {\n \"import\": \"./dist/stubs/playwright-test.mjs\",\n \"require\": \"./dist/stubs/playwright-test.cjs\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","/**\n * Extension HTTP Routes\n *\n * DESIGN PATTERNS:\n * - Hono router for HTTP routing\n * - RESTful API endpoints for extension communication\n * - Dependency injection with Container\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing\n * - Keep route handlers thin, delegate to services\n * - Return appropriate HTTP status codes\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type {\n HandoffRequest,\n IExtensionSessionRegistry,\n SessionHeartbeatRequest,\n SessionRegistrationRequest,\n} from '../services/ExtensionSessionRegistry.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ExtensionTaskQueue, ExtensionTaskResult } from '../services/ExtensionTaskQueue.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport {\n TelemetryService,\n type ITelemetryService,\n resolveBrowserTelemetryConfig,\n} from '../services/TelemetryService.js';\n\n/**\n * Task request body from extension polling\n */\ninterface TaskPollResponse {\n task?: {\n id: string;\n tool: string;\n arguments: Record<string, unknown>;\n telemetry?: {\n traceId?: string;\n parentSpanId?: string;\n };\n };\n}\n\n/**\n * Result submission body from extension\n */\ninterface ResultSubmitRequest {\n taskId: string;\n success: boolean;\n result?: {\n content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n isError?: boolean;\n };\n error?: string;\n}\n\ninterface TabMappedRequest {\n pageId: string;\n tabId: number;\n}\n\ninterface RecordingArtifactRequest {\n browserId: string;\n videoBase64?: string;\n}\n\ninterface RecordingChunkRequest {\n browserId: string;\n chunkBase64: string;\n mimeType?: string;\n chunkIndex?: number;\n}\n\ninterface BrowserLogRequest {\n level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n message: string;\n attributes?: Record<string, unknown>;\n}\n\nconst DEBUG_EXTENSION_RECORDING_STDOUT = process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1';\n\nfunction emitExtensionRecordingDebug(message: string, details?: Record<string, unknown>): void {\n if (!DEBUG_EXTENSION_RECORDING_STDOUT) {\n return;\n }\n\n const payload = details ? ` ${JSON.stringify(details)}` : '';\n console.log(`[ExtensionRecordingDebug] ${message}${payload}`);\n}\n\n/**\n * Status response\n */\ninterface StatusResponse {\n connected: boolean;\n lastPollAt?: string;\n lastResultAt?: string;\n pendingTasks: number;\n queueSize: number;\n}\n\n/**\n * Create extension routes for Chrome extension communication\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono router\n */\nexport function createExtensionRoutes(container: Container): Hono {\n const router = new Hono();\n let telemetry: ITelemetryService;\n try {\n telemetry = container.get<ITelemetryService>(PLAYWRIGHT_TYPES.TelemetryService);\n } catch {\n telemetry = new TelemetryService();\n }\n\n router.get('/telemetry-config', (c) => {\n try {\n const requestOrigin = new URL(c.req.url).origin;\n return c.json(resolveBrowserTelemetryConfig(process.env, requestOrigin));\n } catch (error) {\n return c.json(\n {\n enabled: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/tasks\n * Extension polls for next task to execute\n */\n router.get('/tasks', (c) => {\n try {\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const task = taskQueue.getNextTask();\n\n if (!task) {\n return c.json<TaskPollResponse>({});\n }\n\n return c.json<TaskPollResponse>({\n task: {\n id: task.id,\n tool: task.tool,\n arguments: task.arguments,\n telemetry: task.telemetry,\n },\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/result\n * Extension submits task execution result\n */\n router.post('/result', async (c) => {\n try {\n const body = (await c.req.json()) as ResultSubmitRequest;\n\n if (!body.taskId) {\n return c.json(\n {\n success: false,\n error: 'Missing taskId in request body',\n },\n 400,\n );\n }\n\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n\n const result: ExtensionTaskResult = {\n taskId: body.taskId,\n success: body.success,\n result: body.result,\n error: body.error,\n };\n\n const task = taskQueue.submitResult(result);\n\n if (!task) {\n return c.json(\n {\n success: false,\n error: `Task ${body.taskId} not found or already completed`,\n },\n 404,\n );\n }\n\n if (task.browserId) {\n browserService.recordBrowserActivity(task.browserId, task.pageId);\n }\n\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/status\n * Connection health check and status\n */\n router.get('/status', (c) => {\n try {\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const status = taskQueue.getConnectionStatus();\n\n const response: StatusResponse = {\n connected: status.connected,\n lastPollAt: status.lastPollAt?.toISOString(),\n lastResultAt: status.lastResultAt?.toISOString(),\n pendingTasks: status.pendingTasks,\n queueSize: taskQueue.queueSize,\n };\n\n return c.json(response);\n } catch (error) {\n return c.json(\n {\n connected: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/register\n * Extension registers a stealth browser session\n */\n router.post('/register', async (c) => {\n try {\n const body = (await c.req.json()) as SessionRegistrationRequest;\n\n if (!body.browserId) {\n return c.json(\n {\n success: false,\n error: 'Missing browserId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.register(body);\n\n return c.json({\n success: true,\n session: {\n id: session.id,\n browserId: session.browserId,\n controlMode: session.controlMode,\n createdAt: session.createdAt.toISOString(),\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/heartbeat\n * Extension sends heartbeat to keep session alive\n */\n router.post('/heartbeat', async (c) => {\n try {\n const body = (await c.req.json()) as SessionHeartbeatRequest;\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.heartbeat(body);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n lastHeartbeatAt: session.lastHeartbeatAt.toISOString(),\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/tab-mapped', async (c) => {\n try {\n const body = (await c.req.json()) as TabMappedRequest;\n\n if (!body.pageId || typeof body.tabId !== 'number') {\n return c.json(\n {\n success: false,\n error: 'Missing pageId or tabId in request body',\n },\n 400,\n );\n }\n\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageEntry = pageRegistry.get(body.pageId);\n\n if (!pageEntry) {\n return c.json(\n {\n success: false,\n error: `Page ${body.pageId} not found`,\n },\n 404,\n );\n }\n\n pageEntry.extensionTabId = body.tabId;\n browserService.recordBrowserActivity(pageEntry.browserId, body.pageId);\n\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/recording', async (c) => {\n try {\n const body = (await c.req.json()) as RecordingArtifactRequest;\n\n if (!body.browserId) {\n return c.json(\n {\n success: false,\n error: 'Missing browserId in request body',\n },\n 400,\n );\n }\n\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const persisted = await browserService.persistExtensionRecordingArtifact(body.browserId, body.videoBase64);\n emitExtensionRecordingDebug('artifact received', {\n browserId: body.browserId,\n videoBase64Size: body.videoBase64?.length ?? 0,\n persisted,\n });\n\n if (!persisted) {\n return c.json(\n {\n success: false,\n error: `Browser \"${body.browserId}\" has no active recording target`,\n },\n 404,\n );\n }\n\n browserService.recordBrowserActivity(body.browserId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/recording/chunk', async (c) => {\n try {\n const body = (await c.req.json()) as RecordingChunkRequest;\n\n if (!body.browserId || !body.chunkBase64) {\n return c.json(\n {\n success: false,\n error: 'Missing browserId or chunkBase64 in request body',\n },\n 400,\n );\n }\n\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const persisted = await browserService.persistExtensionRecordingChunk(body.browserId, body.chunkBase64);\n emitExtensionRecordingDebug('chunk received', {\n browserId: body.browserId,\n chunkIndex: body.chunkIndex,\n mimeType: body.mimeType,\n persisted: !!persisted,\n chunkBase64Size: body.chunkBase64.length,\n });\n\n if (!persisted) {\n return c.json(\n {\n success: false,\n error: `Browser \"${body.browserId}\" has no active recording target`,\n },\n 404,\n );\n }\n\n telemetry.log('debug', 'extension recording chunk received', {\n attributes: {\n 'browse_tool.extension.recording.chunk_received': true,\n 'browse_tool.browser.id': body.browserId,\n 'browse_tool.recording.chunk_bytes': persisted.chunkBytes,\n 'browse_tool.recording.total_bytes': persisted.totalBytes,\n 'browse_tool.recording.chunk_count': persisted.chunkCount,\n ...(typeof body.chunkIndex === 'number' ? { 'browse_tool.recording.chunk_index': body.chunkIndex } : {}),\n ...(typeof body.mimeType === 'string' ? { 'browse_tool.recording.mime_type': body.mimeType } : {}),\n },\n });\n\n browserService.recordBrowserActivity(body.browserId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/browser-log', async (c) => {\n try {\n const body = (await c.req.json()) as BrowserLogRequest;\n\n if (!body.message || typeof body.message !== 'string') {\n return c.json(\n {\n success: false,\n error: 'Missing message in request body',\n },\n 400,\n );\n }\n\n telemetry.log(body.level ?? 'info', body.message, {\n attributes: {\n 'browse_tool.extension.log_relay': true,\n ...(typeof body.attributes === 'object' && body.attributes !== null ? body.attributes : {}),\n },\n });\n emitExtensionRecordingDebug('browser log relayed', {\n level: body.level ?? 'info',\n message: body.message,\n attributes: body.attributes,\n });\n\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/handoff\n * Request handoff from spec to AI control\n */\n router.post('/handoff', async (c) => {\n try {\n const body = (await c.req.json()) as HandoffRequest;\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.requestHandoff(body);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n message: 'Handoff requested. AI will take control when ready.',\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/handoff/acknowledge\n * AI acknowledges handoff and takes control\n */\n router.post('/handoff/acknowledge', async (c) => {\n try {\n const body = (await c.req.json()) as { sessionId: string };\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.acknowledgeHandoff(body.sessionId);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found or no handoff pending`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n message: 'Handoff acknowledged. AI now has control.',\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/sessions\n * List all active extension sessions\n */\n router.get('/sessions', (c) => {\n try {\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const sessions = sessionRegistry.listSessions();\n\n return c.json({\n sessions: sessions.map((s) => ({\n id: s.id,\n browserId: s.browserId,\n tabId: s.tabId,\n currentUrl: s.currentUrl,\n controlMode: s.controlMode,\n activeSpecPath: s.activeSpecPath,\n handoffRequested: s.handoffRequested,\n createdAt: s.createdAt.toISOString(),\n lastHeartbeatAt: s.lastHeartbeatAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n return router;\n}\n","/**\n * Chrome Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - HTTP server pattern for Chrome extension polling\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n */\n\nimport { serve } from '@hono/node-server';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { Command } from 'commander';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createExtensionRoutes } from '../server/extension-routes.js';\nimport { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { ExtensionToolDelegator } from '../services/ExtensionToolDelegator.js';\nimport type { ToolDefinition } from '../types/index.js';\n\nexport interface ChromeServeOptions {\n port: number;\n verbose: boolean;\n waitForExtension: boolean;\n}\n\n/**\n * Create extension-specific container module\n */\nfunction createExtensionModule(): ContainerModule {\n return new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(PLAYWRIGHT_TYPES.ExtensionTaskQueue).to(ExtensionTaskQueue).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionToolDelegator).to(ExtensionToolDelegator).inSingletonScope();\n });\n}\n\n/**\n * Create MCP server that delegates to Chrome extension\n */\nfunction createExtensionMcpServer(delegator: ExtensionToolDelegator): Server {\n const server = new Server(\n {\n name: 'browse-tool-chrome',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const supportedTools = delegator.getSupportedTools();\n\n const toolDefinitions: ToolDefinition[] = supportedTools.map((name) => ({\n name,\n description: `Browser automation tool (Chrome extension mode): ${name}`,\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: true,\n },\n }));\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: toolDefinitions };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n return await delegator.executeTool(name, args || {});\n });\n\n return server;\n}\n\nexport const chromeServeCommand = new Command('chrome-serve')\n .description(\n '[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation',\n )\n .option('-p, --port <port>', 'HTTP server port for extension polling', '3200')\n .option('-v, --verbose', 'Enable verbose output', false)\n .option('--wait-for-extension', 'Wait for extension to connect before accepting MCP requests', false)\n .action(async (options: ChromeServeOptions) => {\n // Deprecation warning\n console.error('');\n console.error('╔════════════════════════════════════════════════════════════════╗');\n console.error('║ DEPRECATED: chrome-serve is deprecated. ║');\n console.error('║ Use mcp-serve instead - extension routes are now ║');\n console.error('║ automatically available in the HTTP server at /extension/*. ║');\n console.error('╚════════════════════════════════════════════════════════════════╝');\n console.error('');\n\n try {\n const port = typeof options.port === 'string' ? Number.parseInt(options.port, 10) : options.port;\n\n if (options.verbose) {\n console.error('Chrome Extension MCP Server starting...');\n console.error(` HTTP Port: ${port}`);\n console.error(` Wait for extension: ${options.waitForExtension}`);\n }\n\n // Create container with extension services\n const container = new Container({ defaultScope: 'Singleton' });\n container.load(createExtensionModule());\n\n // Get services\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const delegator = container.get<ExtensionToolDelegator>(PLAYWRIGHT_TYPES.ExtensionToolDelegator);\n\n // Create HTTP server for extension polling\n const app = new Hono();\n\n app.use(\n '*',\n cors({\n origin: (origin) => origin ?? null,\n credentials: true,\n allowMethods: ['GET', 'POST', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Accept'],\n }),\n );\n\n // Mount extension routes\n const extensionRoutes = createExtensionRoutes(container);\n app.route('/extension', extensionRoutes);\n\n // Health check\n app.get('/health', (c) => {\n const status = taskQueue.getConnectionStatus();\n return c.json({\n status: 'healthy',\n service: 'browse-tool-chrome',\n extension: status,\n });\n });\n\n // Start HTTP server\n const httpServer = serve({\n fetch: app.fetch,\n port,\n });\n\n // Handle server errors (e.g., port in use)\n httpServer.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n console.error(`Error [PORT_IN_USE]: Port ${port} is already in use.`);\n console.error('Recovery: Try a different port with --port <port>');\n } else {\n console.error(`Error [SERVER_ERROR]: HTTP server error: ${error.message}`);\n }\n process.exit(1);\n });\n\n console.error(`HTTP server started on port ${port}`);\n console.error('Waiting for Chrome extension to connect...');\n console.error(`Extension should poll: http://localhost:${port}/extension/tasks`);\n\n // Wait for extension if requested\n if (options.waitForExtension) {\n console.error('Waiting for extension connection before starting MCP...');\n await new Promise<void>((resolve) => {\n const checkInterval = setInterval(() => {\n const status = taskQueue.getConnectionStatus();\n if (status.connected) {\n clearInterval(checkInterval);\n console.error('Extension connected!');\n resolve();\n }\n }, 500);\n });\n }\n\n // Create MCP server with extension delegation\n const mcpServer = createExtensionMcpServer(delegator);\n\n // Start stdio transport\n const transport = new StdioServerTransport();\n await mcpServer.connect(transport);\n\n console.error('Chrome extension MCP server started on stdio');\n\n const shutdown = async (signal: string) => {\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n taskQueue.clearAllTasks('Server shutting down');\n await transport.close();\n httpServer.close();\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${errorMessage}`);\n console.error('Recovery: Check that the port is available and try again.');\n process.exit(1);\n }\n });\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport { z } from 'zod';\n\nconst CONFIG_ENV_VAR = 'BROWSE_TOOL_CONFIG';\nconst CONFIG_DIR_NAME = '.browse-tool';\nconst CONFIG_FILE_NAME = 'config.json';\nconst OUTPUT_FORMATS = ['json', 'text', 'quiet'] as const;\nconst DEFAULT_CONFIG: BrowseToolConfig = {\n commands: {},\n tools: {},\n};\n\nconst toolDefaultsSchema = z.record(z.string(), z.unknown());\n\nconst commandDefaultsSchema = z.object({\n mcpServe: z\n .object({\n type: z.string().optional(),\n browser: z.string().optional(),\n headless: z.boolean().optional(),\n profile: z.string().optional(),\n mode: z.string().optional(),\n host: z.string().optional(),\n port: z.coerce.number().int().positive().optional(),\n tags: z.string().optional(),\n exclude: z.string().optional(),\n customTools: z.string().optional(),\n snippetsDir: z.string().optional(),\n registryPath: z.string().optional(),\n registryDir: z.string().optional(),\n pidsDir: z.string().optional(),\n profilesDir: z.string().optional(),\n })\n .partial()\n .optional(),\n httpServe: z\n .object({\n port: z.coerce.number().int().positive().optional(),\n headless: z.boolean().optional(),\n idleTimeout: z.coerce.number().positive().optional(),\n host: z.string().optional(),\n registryDir: z.string().optional(),\n registryPath: z.string().optional(),\n pidsDir: z.string().optional(),\n profilesDir: z.string().optional(),\n snippetsDir: z.string().optional(),\n })\n .partial()\n .optional(),\n exec: z\n .object({\n format: z.enum(OUTPUT_FORMATS).optional(),\n color: z.boolean().optional(),\n port: z.coerce.number().int().positive().optional(),\n })\n .partial()\n .optional(),\n tools: z\n .object({\n format: z.enum(OUTPUT_FORMATS).optional(),\n color: z.boolean().optional(),\n port: z.coerce.number().int().positive().optional(),\n })\n .partial()\n .optional(),\n status: z.record(z.string(), z.unknown()).optional(),\n stop: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst browseToolConfigSchema = z.object({\n commands: commandDefaultsSchema.default({}),\n tools: z.record(z.string(), toolDefaultsSchema).default({}),\n});\n\nexport type BrowseToolConfig = z.infer<typeof browseToolConfigSchema>;\n\nexport interface CliRuntimeContext {\n config: BrowseToolConfig;\n configPath?: string;\n}\n\nexport interface CliRuntimeOptions {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n homeDir?: string;\n}\n\nlet runtimeContext: CliRuntimeContext = {\n config: DEFAULT_CONFIG,\n};\n\nfunction extractExplicitConfigPath(argv: string[]): string | undefined {\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === '--config') {\n return argv[i + 1];\n }\n if (arg.startsWith('--config=')) {\n return arg.slice('--config='.length);\n }\n }\n return undefined;\n}\n\nfunction resolveConfigCandidatePath(candidatePath: string): string {\n const resolvedPath = path.resolve(candidatePath);\n if (!existsSync(resolvedPath)) {\n throw new Error(`Config path not found: ${resolvedPath}`);\n }\n\n if (statSync(resolvedPath).isDirectory()) {\n return path.join(resolvedPath, CONFIG_FILE_NAME);\n }\n\n return resolvedPath;\n}\n\nfunction findConfigPath(argv: string[], options: CliRuntimeOptions): string | undefined {\n const env = options.env ?? process.env;\n const cwd = options.cwd ?? process.cwd();\n const homeDir = options.homeDir ?? homedir();\n\n const explicitPath = extractExplicitConfigPath(argv);\n if (explicitPath) {\n return resolveConfigCandidatePath(explicitPath);\n }\n\n if (env[CONFIG_ENV_VAR]) {\n return resolveConfigCandidatePath(env[CONFIG_ENV_VAR] as string);\n }\n\n const localConfigPath = path.join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n if (existsSync(localConfigPath)) {\n return localConfigPath;\n }\n\n const homeConfigPath = path.join(homeDir, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n if (existsSync(homeConfigPath)) {\n return homeConfigPath;\n }\n\n return undefined;\n}\n\nfunction readConfigFile(configPath: string): BrowseToolConfig {\n const content = readFileSync(configPath, 'utf8');\n const parsed = JSON.parse(content) as unknown;\n return browseToolConfigSchema.parse(parsed);\n}\n\nexport function loadCliRuntimeContext(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n const configPath = findConfigPath(argv, options);\n if (!configPath) {\n return { config: DEFAULT_CONFIG };\n }\n\n return {\n configPath,\n config: readConfigFile(configPath),\n };\n}\n\nexport function initializeCliRuntime(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n runtimeContext = loadCliRuntimeContext(argv, options);\n return runtimeContext;\n}\n\nexport function getCliRuntimeContext(): CliRuntimeContext {\n return runtimeContext;\n}\n\nexport function setCliRuntimeContextForTesting(context: CliRuntimeContext): void {\n runtimeContext = context;\n}\n\nexport function getCommandConfig<T extends Record<string, unknown>>(\n commandName: keyof BrowseToolConfig['commands'],\n): T {\n const commands = runtimeContext.config.commands ?? {};\n return (commands[commandName] ?? {}) as T;\n}\n\nexport function getToolConfig(toolName: string): Record<string, unknown> {\n return runtimeContext.config.tools?.[toolName] ?? {};\n}\n\nexport function resolveConfiguredOption<T>(\n command: Command,\n optionName: string,\n currentValue: T,\n configValue?: T,\n envValue?: T,\n): T {\n const source = command.getOptionValueSourceWithGlobals(optionName);\n if (source !== undefined && source !== 'default' && source !== 'implied') {\n return currentValue;\n }\n\n if (envValue !== undefined) {\n return envValue;\n }\n\n if (configValue !== undefined) {\n return configValue;\n }\n\n return currentValue;\n}\n","/**\n * HTTP Client Utility for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Service pattern for HTTP communication\n * - Dependency injection via container\n * - Error handling with descriptive messages\n *\n * CODING STANDARDS:\n * - Use async/await for all HTTP operations\n * - Return typed responses\n * - Handle connection errors gracefully\n *\n * AVOID:\n * - Hardcoded URLs (use server discovery)\n * - Missing error handling\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createMcpContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport type { ToolDefinition } from '../types/index.js';\n\n/**\n * Response from /tools endpoint\n */\ninterface ToolsResponse {\n tools: ToolDefinition[];\n error?: string;\n}\n\nexport interface CustomToolDefinition {\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: ToolDefinition['inputSchema'];\n capabilities?: Record<string, unknown>;\n}\n\ninterface CustomToolsResponse {\n tools: CustomToolDefinition[];\n error?: string;\n}\n\n/**\n * Browser info response from /browsers\n */\nexport interface BrowserInfo {\n id: string;\n profileName?: string;\n pageIds: string[];\n currentPageId?: string;\n createdAt: string;\n}\n\ninterface BrowsersResponse {\n browsers: BrowserInfo[];\n error?: string;\n}\n\n/**\n * Response from /execute endpoint\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: CallToolResult;\n error?: string;\n}\n\n/**\n * Client options\n */\nexport interface ToolClientOptions {\n /** Port to use for HTTP server */\n port?: number;\n /** When true, only connect to or spawn the exact requested port */\n exactPort?: boolean;\n /** Timeout for HTTP requests in milliseconds */\n timeout?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60000;\n\n/**\n * HTTP client for executing tools via the browse-tool HTTP server.\n * Handles server discovery/startup and provides typed methods for tool operations.\n */\nexport class ToolClient {\n private readonly port: number;\n private readonly exactPort: boolean;\n private readonly timeout: number;\n private serverPort: number | null = null;\n\n constructor(options: ToolClientOptions = {}) {\n this.port = options.port ?? 3200;\n this.exactPort = options.exactPort ?? false;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Ensure HTTP server is running and return its port\n */\n private async ensureServer(): Promise<number> {\n if (this.serverPort !== null) {\n return this.serverPort;\n }\n\n const container = createMcpContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n const status = await httpServerManager.ensureRunning(this.port, { exactPort: this.exactPort });\n\n if (!status.running || !status.port) {\n throw new Error(status.error ?? 'Failed to start HTTP server. Try running \"browse-tool http-serve\" manually.');\n }\n\n this.serverPort = status.port;\n return status.port;\n }\n\n /**\n * Get base URL for HTTP server\n */\n private async getBaseUrl(): Promise<string> {\n const port = await this.ensureServer();\n return `http://localhost:${port}`;\n }\n\n /**\n * List all available tools from the HTTP server\n */\n async listTools(): Promise<ToolDefinition[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${baseUrl}/tools`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as ToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n return data.tools;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * List custom tools exposed by the HTTP server for a given directory\n */\n async listCustomTools(directory: string): Promise<CustomToolDefinition[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const url = new URL('/custom-tools', baseUrl);\n url.searchParams.set('dir', directory);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as CustomToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n return data.tools;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * List active browsers from the HTTP server\n */\n async listBrowsers(): Promise<BrowserInfo[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${baseUrl}/browsers`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as BrowsersResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n return data.browsers;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Execute a tool with the given arguments\n */\n async execute(tool: string, args: Record<string, unknown>): Promise<CallToolResult> {\n const baseUrl = await this.getBaseUrl();\n // run_spec can execute long-running suites. Do not enforce client-side request timeout.\n const timeoutMs = tool === 'run_spec' ? undefined : this.timeout;\n const controller = new AbortController();\n const timeoutId = timeoutMs !== undefined ? setTimeout(() => controller.abort(), timeoutMs) : undefined;\n\n try {\n const response = await fetch(`${baseUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n tool,\n arguments: args,\n }),\n signal: timeoutMs !== undefined ? controller.signal : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n return {\n content: [{ type: 'text', text: data.error ?? 'Unknown error' }],\n isError: true,\n };\n }\n\n return (\n data.result ?? {\n content: [{ type: 'text', text: 'No result returned' }],\n }\n );\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n /**\n * Execute a custom tool exposed by the HTTP server for a given directory\n */\n async executeCustomTool(directory: string, tool: string, args: Record<string, unknown>): Promise<CallToolResult> {\n const baseUrl = await this.getBaseUrl();\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const url = new URL('/custom-tools', baseUrl);\n url.searchParams.set('dir', directory);\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n tool,\n arguments: args,\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n return {\n content: [{ type: 'text', text: data.error ?? 'Unknown error' }],\n isError: true,\n };\n }\n\n return (\n data.result ?? {\n content: [{ type: 'text', text: 'No result returned' }],\n }\n );\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Wrap connection errors with helpful recovery messages\n */\n private wrapConnectionError(error: unknown): Error {\n if (error instanceof Error) {\n if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch failed')) {\n return new Error(\n `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: ${error.message}`,\n { cause: error },\n );\n }\n return error;\n }\n return new Error(String(error), { cause: error });\n }\n}\n\n/**\n * Create a new ToolClient instance\n */\nexport function createToolClient(options?: ToolClientOptions): ToolClient {\n return new ToolClient(options);\n}\n","/**\n * Output Formatter Utilities for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Pure functions with no side effects\n * - Single responsibility per function\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - External dependencies (keep utilities pure)\n */\n\nimport type { CallToolResult, ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Output format options\n */\nexport type OutputFormat = 'json' | 'text' | 'quiet';\n\n/**\n * Formatter options\n */\nexport interface FormatterOptions {\n format: OutputFormat;\n color: boolean;\n}\n\n/**\n * ANSI color codes for terminal output\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n magenta: '\\x1b[35m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m',\n} as const;\n\n/**\n * Apply color to text if color is enabled\n */\nexport function colorize(text: string, color: keyof typeof COLORS, useColor: boolean): string {\n if (!useColor) {\n return text;\n }\n return `${COLORS[color]}${text}${COLORS.reset}`;\n}\n\n/**\n * Format a CallToolResult for CLI output\n */\nexport function formatToolResult(result: CallToolResult, options: FormatterOptions): string {\n const { format, color } = options;\n\n if (format === 'quiet') {\n return '';\n }\n\n if (format === 'json') {\n return formatAsJson(result);\n }\n\n return formatAsText(result, color);\n}\n\n/**\n * Format result as JSON\n */\nexport function formatAsJson(result: CallToolResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Format result as human-readable text\n */\nexport function formatAsText(result: CallToolResult, useColor: boolean): string {\n const lines: string[] = [];\n\n if (result.isError) {\n lines.push(colorize('Error:', 'red', useColor));\n }\n\n for (const content of result.content) {\n if (content.type === 'text') {\n lines.push(formatTextContent(content as TextContent, useColor));\n } else if (content.type === 'image') {\n lines.push(formatImageContent(content as ImageContent, useColor));\n } else {\n lines.push(colorize(`[Unknown content type: ${content.type}]`, 'yellow', useColor));\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format text content\n */\nexport function formatTextContent(content: TextContent, useColor: boolean): string {\n const text = content.text;\n\n // Try to parse as JSON for pretty printing\n try {\n const parsed = JSON.parse(text);\n return formatParsedJson(parsed, useColor);\n } catch {\n // Not JSON, return as-is\n return text;\n }\n}\n\n/**\n * Format parsed JSON object with optional coloring\n */\nexport function formatParsedJson(data: unknown, useColor: boolean): string {\n if (typeof data !== 'object' || data === null) {\n return String(data);\n }\n\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(data)) {\n const coloredKey = colorize(key, 'cyan', useColor);\n const formattedValue = formatValue(value, useColor);\n lines.push(`${coloredKey}: ${formattedValue}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format a value with type-appropriate coloring\n */\nexport function formatValue(value: unknown, useColor: boolean): string {\n if (value === null) {\n return colorize('null', 'gray', useColor);\n }\n\n if (value === undefined) {\n return colorize('undefined', 'gray', useColor);\n }\n\n if (typeof value === 'boolean') {\n return colorize(String(value), value ? 'green' : 'red', useColor);\n }\n\n if (typeof value === 'number') {\n return colorize(String(value), 'yellow', useColor);\n }\n\n if (typeof value === 'string') {\n // Check if it's a URL\n if (value.startsWith('http://') || value.startsWith('https://')) {\n return colorize(value, 'blue', useColor);\n }\n return value;\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return colorize('[]', 'gray', useColor);\n }\n return JSON.stringify(value, null, 2);\n }\n\n if (typeof value === 'object') {\n return JSON.stringify(value, null, 2);\n }\n\n return String(value);\n}\n\n/**\n * Format image content\n */\nexport function formatImageContent(content: ImageContent, useColor: boolean): string {\n const { mimeType, data } = content;\n const sizeKb = Math.round((data.length * 3) / 4 / 1024);\n\n return colorize(`[Image: ${mimeType}, ~${sizeKb}KB base64 data]`, 'magenta', useColor);\n}\n\n/**\n * Format an error for CLI output\n */\nexport function formatError(error: Error | string, useColor: boolean): string {\n const message = error instanceof Error ? error.message : error;\n return colorize(`Error: ${message}`, 'red', useColor);\n}\n\n/**\n * Format a success message for CLI output\n */\nexport function formatSuccess(message: string, useColor: boolean): string {\n return colorize(`✓ ${message}`, 'green', useColor);\n}\n\n/**\n * Format a warning message for CLI output\n */\nexport function formatWarning(message: string, useColor: boolean): string {\n return colorize(`⚠ ${message}`, 'yellow', useColor);\n}\n\n/**\n * Format a list of tools for help output\n */\nexport function formatToolList(tools: Array<{ name: string; description: string }>, useColor: boolean): string {\n const maxNameLength = Math.max(...tools.map((t) => t.name.length));\n const lines: string[] = [];\n\n for (const tool of tools) {\n const paddedName = tool.name.padEnd(maxNameLength);\n const coloredName = colorize(paddedName, 'cyan', useColor);\n lines.push(` ${coloredName} ${tool.description}`);\n }\n\n return lines.join('\\n');\n}\n","import { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createToolClient } from '../utils/httpClient.js';\nimport { DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport {\n type FormatterOptions,\n type OutputFormat,\n formatError,\n formatToolList,\n formatToolResult,\n} from '../utils/outputFormatter.js';\n\ninterface CustomToolsCommandOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\nfunction resolveCommonOptions(\n command: Command,\n options: CustomToolsCommandOptions,\n): {\n port: string;\n formatterOptions: FormatterOptions;\n} {\n const commandDefaults = getCommandConfig<{\n format?: OutputFormat;\n color?: boolean;\n port?: number;\n }>('tools');\n\n const format = resolveConfiguredOption(command, 'format', options.format as OutputFormat, commandDefaults.format);\n const color = resolveConfiguredOption(command, 'color', options.color, commandDefaults.color);\n const port = resolveConfiguredOption(\n command,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n\n return {\n port,\n formatterOptions: {\n format,\n color,\n },\n };\n}\n\nexport const listCustomToolsCommand = new Command('list-custom-tools')\n .description('List custom tools from a tools directory')\n .argument('<dir>', 'Path to the tools directory containing tools.yaml')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n .action(async function (this: Command, dir: string, options: CustomToolsCommandOptions) {\n const { port, formatterOptions } = resolveCommonOptions(this, options);\n\n try {\n const client = createToolClient({\n port: Number.parseInt(port, 10),\n });\n\n const tools = await client.listCustomTools(dir);\n const output =\n formatterOptions.format === 'text'\n ? formatToolList(tools, formatterOptions.color)\n : formatterOptions.format === 'quiet'\n ? ''\n : JSON.stringify(tools, null, 2);\n\n if (output) {\n console.log(output);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n\nexport const execCustomToolCommand = new Command('exec-custom-tool')\n .description('Execute a custom tool from a tools directory')\n .argument('<dir>', 'Path to the tools directory containing tools.yaml')\n .argument('<tool>', 'Custom tool name to execute')\n .argument('[args]', 'JSON arguments for the tool', '{}')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n .action(async function (\n this: Command,\n dir: string,\n tool: string,\n argsJson: string,\n options: CustomToolsCommandOptions,\n ) {\n const { port, formatterOptions } = resolveCommonOptions(this, options);\n\n try {\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsJson);\n } catch {\n console.error(formatError(`Invalid JSON arguments: ${argsJson}`, formatterOptions.color));\n process.exit(1);\n return;\n }\n\n const client = createToolClient({\n port: Number.parseInt(port, 10),\n });\n\n const result = await client.executeCustomTool(dir, tool, args);\n const output = formatToolResult(result, formatterOptions);\n if (output) {\n console.log(output);\n }\n\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n\nexport const customToolsCommand = new Command('custom-tools')\n .description('List and execute custom tools through the HTTP server')\n .addCommand(listCustomToolsCommand)\n .addCommand(execCustomToolCommand);\n","import { Command } from 'commander';\nimport chromeForTestingConfig from '../config/chrome-for-testing.json';\nimport {\n buildDockerChromeForTestingImage,\n resolveChromeForTestingArchivePlatform,\n} from '../utils/dockerChromeForTesting.js';\n\ninterface DockerBuildCftOptions {\n cftVersion?: string;\n image?: string;\n platform?: string;\n}\n\nexport const dockerBuildCftCommand = new Command('docker-build-cft')\n .description('Build the Chrome for Testing Docker image used by vm mode')\n .option('--cft-version <version>', 'Chrome for Testing version to build', chromeForTestingConfig.version)\n .option('--image <image>', 'Docker image tag to produce', chromeForTestingConfig.dockerImage)\n .option('--platform <platform>', 'Docker target platform', chromeForTestingConfig.dockerPlatform)\n .action(async (options: DockerBuildCftOptions) => {\n try {\n const result = await buildDockerChromeForTestingImage({\n version: options.cftVersion,\n image: options.image,\n platform: options.platform,\n stdio: 'inherit',\n });\n\n console.log(`Built Docker image ${result.image}`);\n console.log(` Version: ${result.version}`);\n console.log(` Platform: ${result.platform}`);\n console.log(` Archive: ${resolveChromeForTestingArchivePlatform(result.platform)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n });\n","/**\n * Exec Command\n *\n * Execute a tool directly with JSON arguments. Power user command for raw API access.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() and .argument() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createToolClient } from '../utils/httpClient.js';\nimport { DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport { type FormatterOptions, type OutputFormat, formatError, formatToolResult } from '../utils/outputFormatter.js';\n\ninterface ExecOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\n/**\n * Execute a tool directly with JSON arguments\n */\nexport const execCommand = new Command('exec')\n .description('Execute a tool directly with JSON arguments')\n .argument('<tool>', 'Tool name to execute (e.g., browser_launch)')\n .argument('[args]', 'JSON arguments for the tool', '{}')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n .action(async function (this: Command, tool: string, argsJson: string, options: ExecOptions) {\n const commandDefaults = getCommandConfig<{\n format?: OutputFormat;\n color?: boolean;\n port?: number;\n }>('exec');\n const format = resolveConfiguredOption(this, 'format', options.format as OutputFormat, commandDefaults.format);\n const color = resolveConfiguredOption(this, 'color', options.color, commandDefaults.color);\n const port = resolveConfiguredOption(\n this,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n const portSource = this.getOptionValueSourceWithGlobals('port');\n const exactPort = portSource !== undefined && portSource !== 'default' && portSource !== 'implied';\n const formatterOptions: FormatterOptions = {\n format,\n color,\n };\n\n try {\n // Parse JSON arguments\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsJson);\n } catch {\n console.error(formatError(`Invalid JSON arguments: ${argsJson}`, formatterOptions.color));\n process.exit(1);\n }\n\n // Create client and execute\n const client = createToolClient({\n port: Number.parseInt(port, 10),\n exactPort,\n });\n\n const result = await client.execute(tool, args);\n\n // Format and output result\n const output = formatToolResult(result, formatterOptions);\n if (output) {\n console.log(output);\n }\n\n // Exit with error code if tool failed\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n","import { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { stripTypeScriptTypes } from 'node:module';\nimport type { Attributes } from '@opentelemetry/api';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport type { IPageRegistry, PageEntry } from './PageRegistry.js';\nimport type { BrowserMode, IBrowserService } from './BrowserService.js';\nimport { ExtensionPageProxy, type IExtensionPageProxy } from './ExtensionPageProxy.js';\nimport type { ExtensionTaskQueue } from './ExtensionTaskQueue.js';\nimport { TelemetryService, type ITelemetryService } from './TelemetryService.js';\n\nconst MANIFEST_FILE = 'tools.yaml';\nconst REQUIRED_PAGE_ID_FIELD = 'pageId';\nconst OPTIONAL_BROWSER_ID_FIELD = 'browserId';\n\nconst ToolCapabilitiesSchema = z.record(z.string(), z.unknown());\n\nconst ToolInputSchemaSchema = z\n .object({\n type: z.literal('object'),\n properties: z.record(z.string(), z.unknown()).optional(),\n required: z.array(z.string()).optional(),\n additionalProperties: z.boolean().optional(),\n })\n .passthrough();\n\nconst CustomToolManifestEntrySchema = z.object({\n name: z.string().min(1),\n description: z.string().min(1),\n script: z.string().min(1),\n suggestionActions: z.string().min(1).optional(),\n capabilities: ToolCapabilitiesSchema,\n inputSchema: ToolInputSchemaSchema,\n});\n\nconst CustomToolManifestSchema = z.object({\n tools: z.array(CustomToolManifestEntrySchema),\n});\n\ntype CustomToolManifestEntry = z.infer<typeof CustomToolManifestEntrySchema>;\n\nexport interface CustomToolDefinition {\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: z.infer<typeof ToolInputSchemaSchema>;\n capabilities: z.infer<typeof ToolCapabilitiesSchema>;\n}\n\ninterface LoadedCustomTool extends CustomToolDefinition {\n scriptPath: string;\n execute?: CustomToolHandler;\n}\n\ntype CustomToolPage = NonNullable<PageEntry['page']> | IExtensionPageProxy;\n\nexport interface CustomToolBrowserPageSummary {\n pageId: string;\n url: string;\n title: string;\n active: boolean;\n}\n\nexport interface CustomToolBrowserPageHandle extends CustomToolBrowserPageSummary {\n page: CustomToolPage;\n}\n\nexport interface CustomToolBrowser {\n readonly browserId: string;\n readonly mode: BrowserMode;\n listPages(): Promise<CustomToolBrowserPageSummary[]>;\n getPage(pageId: string): Promise<CustomToolBrowserPageHandle>;\n getCurrentPage(): Promise<CustomToolBrowserPageHandle>;\n newPage(options?: { url?: string; setAsCurrent?: boolean }): Promise<CustomToolBrowserPageHandle>;\n}\n\nexport interface CustomToolLogOptions {\n attributes?: Record<string, unknown>;\n exception?: unknown;\n}\n\nexport interface CustomToolLogger {\n getTraceContext(): { traceId?: string; spanId?: string };\n trace(message: string, options?: CustomToolLogOptions): void;\n debug(message: string, options?: CustomToolLogOptions): void;\n info(message: string, options?: CustomToolLogOptions): void;\n warn(message: string, options?: CustomToolLogOptions): void;\n error(message: string, options?: CustomToolLogOptions): void;\n fatal(message: string, options?: CustomToolLogOptions): void;\n}\n\ntype CustomToolHandler = (context: {\n page: CustomToolPage;\n browser: CustomToolBrowser;\n input: Record<string, unknown>;\n logger: CustomToolLogger;\n}) => unknown;\n\ninterface LoadedCustomToolModule {\n run?: unknown;\n default?: unknown;\n}\n\ninterface YamlLine {\n indent: number;\n text: string;\n}\n\nconst CUSTOM_TOOL_DEBUG_ENABLED = process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS === '1';\nconst CUSTOM_TOOL_PAGE_METADATA_WAIT_TIMEOUT_MS = 1500;\nconst CUSTOM_TOOL_PAGE_METADATA_WAIT_POLL_MS = 50;\n\nfunction summarizeValue(value: unknown): string {\n return JSON.stringify(value, (_key, current) => {\n if (typeof current === 'string' && current.length > 240) {\n return `${current.slice(0, 240)}...<trimmed>`;\n }\n return current;\n });\n}\n\nfunction debugCustomTool(message: string, details?: Record<string, unknown>): void {\n if (!CUSTOM_TOOL_DEBUG_ENABLED) {\n return;\n }\n\n if (details) {\n console.error(`[CustomToolService] ${message}`, details);\n return;\n }\n\n console.error(`[CustomToolService] ${message}`);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isCallToolResult(value: unknown): value is CallToolResult {\n return isPlainObject(value) && Array.isArray(value.content);\n}\n\nfunction normalizeScalar(rawValue: string): unknown {\n const value = rawValue.trim();\n if (value === '') {\n return '';\n }\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith('[') && value.endsWith(']'))) {\n return JSON.parse(value);\n }\n if (value.startsWith('{') && value.endsWith('}')) {\n return JSON.parse(value);\n }\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n return value.slice(1, -1);\n }\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n if (value === 'null') {\n return null;\n }\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return Number(value);\n }\n return value;\n}\n\nfunction toAttributes(input: Record<string, unknown> | undefined): Attributes | undefined {\n if (!input || Object.keys(input).length === 0) {\n return undefined;\n }\n\n const attributes: Attributes = {};\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined || value === null) {\n continue;\n }\n\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n attributes[key] = value;\n continue;\n }\n\n attributes[key] = JSON.stringify(value);\n }\n\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n}\n\nfunction toYamlLines(input: string): YamlLine[] {\n return input\n .replace(/^\\uFEFF/, '')\n .split(/\\r?\\n/)\n .map((line) => {\n if (line.includes('\\t')) {\n throw new Error('Tab indentation is not supported in tools.yaml');\n }\n\n const withoutComment = line.replace(/\\s+#.*$/, '');\n if (withoutComment.trim().length === 0) {\n return null;\n }\n\n const indent = withoutComment.match(/^ */)?.[0].length ?? 0;\n return {\n indent,\n text: withoutComment.trim(),\n };\n })\n .filter((line): line is YamlLine => line !== null);\n}\n\nfunction parseYamlValue(lines: YamlLine[], startIndex: number, indent: number): [unknown, number] {\n const line = lines[startIndex];\n if (!line || line.indent !== indent) {\n throw new Error(`Invalid indentation in tools.yaml at line ${startIndex + 1}`);\n }\n\n if (line.text.startsWith('-')) {\n return parseYamlArray(lines, startIndex, indent);\n }\n\n return parseYamlObject(lines, startIndex, indent);\n}\n\nfunction parseYamlArray(lines: YamlLine[], startIndex: number, indent: number): [unknown[], number] {\n const result: unknown[] = [];\n let index = startIndex;\n\n while (index < lines.length) {\n const line = lines[index];\n if (line.indent < indent) {\n break;\n }\n if (line.indent !== indent || !line.text.startsWith('-')) {\n break;\n }\n\n const itemText = line.text.slice(1).trim();\n if (itemText === '') {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= indent) {\n result.push(null);\n index += 1;\n continue;\n }\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n result.push(nestedValue);\n index = nextIndex;\n continue;\n }\n\n if (itemText.includes(':')) {\n const [key, rawValue] = splitYamlKeyValue(itemText);\n const objectValue: Record<string, unknown> = {};\n\n if (rawValue === undefined) {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= indent) {\n throw new Error(`Expected nested value for \"${key}\" in tools.yaml`);\n }\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n objectValue[key] = nestedValue;\n index = nextIndex;\n } else {\n objectValue[key] = normalizeScalar(rawValue);\n index += 1;\n }\n\n while (index < lines.length && lines[index].indent > indent) {\n const nestedLine = lines[index];\n if (nestedLine.indent !== indent + 2 || nestedLine.text.startsWith('-')) {\n const [nestedValue, nextIndex] = parseYamlValue(lines, index, nestedLine.indent);\n if (!isPlainObject(nestedValue)) {\n throw new Error(`Expected object entry in tools.yaml at line ${index + 1}`);\n }\n Object.assign(objectValue, nestedValue);\n index = nextIndex;\n continue;\n }\n\n const [nestedKey, nestedRawValue] = splitYamlKeyValue(nestedLine.text);\n if (nestedRawValue === undefined) {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= nestedLine.indent) {\n throw new Error(`Expected nested value for \"${nestedKey}\" in tools.yaml`);\n }\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n objectValue[nestedKey] = nestedValue;\n index = nextIndex;\n continue;\n }\n\n objectValue[nestedKey] = normalizeScalar(nestedRawValue);\n index += 1;\n }\n\n result.push(objectValue);\n continue;\n }\n\n result.push(normalizeScalar(itemText));\n index += 1;\n }\n\n return [result, index];\n}\n\nfunction parseYamlObject(lines: YamlLine[], startIndex: number, indent: number): [Record<string, unknown>, number] {\n const result: Record<string, unknown> = {};\n let index = startIndex;\n\n while (index < lines.length) {\n const line = lines[index];\n if (line.indent < indent) {\n break;\n }\n if (line.indent !== indent || line.text.startsWith('-')) {\n break;\n }\n\n const [key, rawValue] = splitYamlKeyValue(line.text);\n\n if (rawValue === undefined) {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= indent) {\n result[key] = null;\n index += 1;\n continue;\n }\n\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n result[key] = nestedValue;\n index = nextIndex;\n continue;\n }\n\n result[key] = normalizeScalar(rawValue);\n index += 1;\n }\n\n return [result, index];\n}\n\nfunction splitYamlKeyValue(input: string): [string, string | undefined] {\n const separatorIndex = input.indexOf(':');\n if (separatorIndex === -1) {\n throw new Error(`Invalid tools.yaml entry: \"${input}\"`);\n }\n\n const key = input.slice(0, separatorIndex).trim();\n const rawValue = input.slice(separatorIndex + 1).trim();\n return [key, rawValue === '' ? undefined : rawValue];\n}\n\nfunction parseYamlDocument(input: string): unknown {\n const lines = toYamlLines(input);\n if (lines.length === 0) {\n return {};\n }\n const [value] = parseYamlValue(lines, 0, lines[0].indent);\n return value;\n}\n\nfunction ensureExecutionTargetSchema(tool: CustomToolManifestEntry): void {\n const properties = tool.inputSchema.properties;\n\n if (!properties || !isPlainObject(properties)) {\n throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties`);\n }\n\n const pageIdDefinition = properties[REQUIRED_PAGE_ID_FIELD];\n const browserIdDefinition = properties[OPTIONAL_BROWSER_ID_FIELD];\n const hasPageId =\n pageIdDefinition !== undefined && isPlainObject(pageIdDefinition) && pageIdDefinition.type === 'string';\n const hasBrowserId =\n browserIdDefinition !== undefined && isPlainObject(browserIdDefinition) && browserIdDefinition.type === 'string';\n\n if (!hasPageId && !hasBrowserId) {\n throw new Error(\n `Custom tool \"${tool.name}\" must define inputSchema.properties.pageId and/or inputSchema.properties.browserId as type \"string\"`,\n );\n }\n\n if (pageIdDefinition !== undefined && !hasPageId) {\n throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties.pageId as type \"string\"`);\n }\n\n if (browserIdDefinition !== undefined && !hasBrowserId) {\n throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties.browserId as type \"string\"`);\n }\n}\n\nasync function loadCustomToolModule(scriptPath: string): Promise<LoadedCustomToolModule> {\n const source = await readFile(scriptPath, 'utf8');\n const compiledSource = stripTypeScriptTypes(source, { mode: 'strip' });\n const moduleUrl = `data:text/javascript;base64,${Buffer.from(compiledSource, 'utf8').toString('base64')}`;\n return (await import(moduleUrl)) as LoadedCustomToolModule;\n}\n\nfunction validateCustomToolModule(\n scriptPath: string,\n loadedModule: LoadedCustomToolModule,\n): {\n execute?: CustomToolHandler;\n} {\n const execute = typeof loadedModule.run === 'function' ? (loadedModule.run as CustomToolHandler) : undefined;\n\n if (!execute) {\n throw new Error(`Custom tool script \"${scriptPath}\" must export a \"run\" function`);\n }\n\n return { execute };\n}\n\nfunction injectSuggestionActionsIntoObject(\n value: Record<string, unknown>,\n suggestionActions: string,\n): Record<string, unknown> {\n if (typeof value.suggestionActions === 'string' && value.suggestionActions.trim().length > 0) {\n return value;\n }\n\n return {\n ...value,\n suggestionActions,\n };\n}\n\nfunction injectSuggestionActionsIntoCallToolResult(result: CallToolResult, suggestionActions: string): CallToolResult {\n const firstContent = result.content[0];\n if (firstContent?.type === 'text' && typeof firstContent.text === 'string') {\n try {\n const parsed = JSON.parse(firstContent.text);\n if (isPlainObject(parsed)) {\n const merged = injectSuggestionActionsIntoObject(parsed, suggestionActions);\n return {\n ...result,\n content: [{ ...firstContent, text: JSON.stringify(merged, null, 2) }, ...result.content.slice(1)],\n };\n }\n } catch {\n // Keep the original text payload when it is not valid JSON.\n }\n }\n\n return {\n ...result,\n content: [\n ...result.content,\n {\n type: 'text',\n text: `suggestionActions: ${suggestionActions}`,\n },\n ],\n };\n}\n\nfunction normalizeExecutionResult(toolName: string, value: unknown, suggestionActions?: string): CallToolResult {\n if (isCallToolResult(value)) {\n return suggestionActions ? injectSuggestionActionsIntoCallToolResult(value, suggestionActions) : value;\n }\n\n if (typeof value === 'string') {\n if (!suggestionActions) {\n return { content: [{ type: 'text', text: value }] };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ result: value, suggestionActions }, null, 2),\n },\n ],\n };\n }\n\n if (value === undefined) {\n if (!suggestionActions) {\n return { content: [{ type: 'text', text: `Custom tool \"${toolName}\" completed successfully` }] };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n result: `Custom tool \"${toolName}\" completed successfully`,\n suggestionActions,\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n const normalizedValue =\n isPlainObject(value) && suggestionActions ? injectSuggestionActionsIntoObject(value, suggestionActions) : value;\n return {\n content: [{ type: 'text', text: JSON.stringify(normalizedValue, null, 2) }],\n };\n}\n\nexport class CustomToolService {\n constructor(\n private readonly pageRegistry: IPageRegistry,\n private readonly extensionTaskQueue?: ExtensionTaskQueue,\n private readonly telemetry: ITelemetryService = new TelemetryService(),\n private readonly browserService?: IBrowserService,\n ) {}\n\n private resolveToolPage(toolName: string, pageId: string, pageEntry: PageEntry): CustomToolPage {\n if (pageEntry.page) {\n return pageEntry.page;\n }\n\n if (pageEntry.mode === 'extension' && this.extensionTaskQueue) {\n const proxy = new ExtensionPageProxy(this.extensionTaskQueue);\n proxy.setTarget(pageId, pageEntry.browserId);\n return proxy;\n }\n\n throw new Error(`Custom tool \"${toolName}\" requires a supported page context`);\n }\n\n private toPageSummary(currentPageId: string | null, entry: PageEntry): CustomToolBrowserPageSummary {\n return {\n pageId: entry.id,\n url: entry.url,\n title: entry.title,\n active: currentPageId === entry.id,\n };\n }\n\n private async createPageForBrowser(\n toolName: string,\n browserId: string,\n options?: { url?: string; setAsCurrent?: boolean },\n ): Promise<CustomToolBrowserPageHandle> {\n if (!this.browserService) {\n throw new Error(`Custom tool \"${toolName}\" requires browser service support to create a page`);\n }\n\n const browserInstance = this.browserService.getBrowser(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n const setAsCurrent = options?.setAsCurrent !== false;\n\n if (browserInstance.mode === 'extension' || browserInstance.mode === 'vm') {\n if (!this.extensionTaskQueue) {\n throw new Error(`Custom tool \"${toolName}\" requires extension task support to create a page`);\n }\n\n const pageId = this.pageRegistry.registerExtensionPage(browserId, undefined, options?.url, false);\n\n try {\n const queued = await this.extensionTaskQueue.queueTask(\n 'browser_new_page',\n {\n browserId,\n pageId,\n url: options?.url,\n setAsCurrent,\n },\n 10_000,\n browserId,\n );\n\n if (!queued.success) {\n throw new Error(queued.error ?? `Custom tool \"${toolName}\" failed to create a page`);\n }\n\n const delegatedText = queued.result?.content[0]?.type === 'text' ? queued.result.content[0].text : undefined;\n let delegatedPayload: { url?: string; title?: string; tabId?: number } = {};\n if (typeof delegatedText === 'string' && delegatedText.length > 0) {\n try {\n delegatedPayload = JSON.parse(delegatedText) as { url?: string; title?: string; tabId?: number };\n } catch {\n delegatedPayload = {};\n }\n }\n\n const pageEntry = this.pageRegistry.get(pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageId}\" was not registered`);\n }\n\n pageEntry.url = delegatedPayload.url ?? pageEntry.url;\n pageEntry.title = delegatedPayload.title ?? pageEntry.title;\n pageEntry.extensionTabId = delegatedPayload.tabId ?? pageEntry.extensionTabId;\n const resolvedPageEntry = await this.waitForResolvedPageMetadata(pageId);\n browserInstance.pageIds.add(pageId);\n if (setAsCurrent || !browserInstance.currentPageId) {\n this.browserService.setCurrentPage(browserId, pageId);\n }\n this.browserService.recordBrowserActivity(browserId, pageId);\n\n return {\n ...this.toPageSummary(browserInstance.currentPageId, resolvedPageEntry),\n page: this.resolveToolPage(toolName, pageId, resolvedPageEntry),\n };\n } catch (error) {\n this.pageRegistry.remove(pageId);\n throw error;\n }\n }\n\n const { pageId, page } = await this.browserService.newPage(browserId);\n if (options?.url) {\n await page.goto(options.url);\n await this.pageRegistry.updateMetadata(pageId);\n }\n\n if (setAsCurrent) {\n this.browserService.setCurrentPage(browserId, pageId);\n }\n this.browserService.recordBrowserActivity(browserId, pageId);\n\n const pageEntry = this.pageRegistry.get(pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageId}\" was not registered`);\n }\n\n return {\n ...this.toPageSummary(browserInstance.currentPageId, pageEntry),\n page: this.resolveToolPage(toolName, pageId, pageEntry),\n };\n }\n\n private async waitForResolvedPageMetadata(pageId: string): Promise<PageEntry> {\n const startedAt = Date.now();\n let entry = this.pageRegistry.get(pageId);\n\n while (entry && Date.now() - startedAt < CUSTOM_TOOL_PAGE_METADATA_WAIT_TIMEOUT_MS) {\n const hasUrl = typeof entry.url === 'string' && entry.url.length > 0;\n const hasTitle = typeof entry.title === 'string' && entry.title.length > 0 && entry.title !== 'Extension Tab';\n if (hasUrl && hasTitle) {\n return entry;\n }\n\n await new Promise((resolve) => setTimeout(resolve, CUSTOM_TOOL_PAGE_METADATA_WAIT_POLL_MS));\n entry = this.pageRegistry.get(pageId);\n }\n\n if (!entry) {\n throw new Error(`Page \"${pageId}\" was not registered`);\n }\n\n return entry;\n }\n\n private async resolveExecutionContext(\n toolName: string,\n input: Record<string, unknown>,\n ): Promise<{ pageId: string; pageEntry: PageEntry; page: CustomToolPage; browser: CustomToolBrowser }> {\n const requestedPageId =\n typeof input[REQUIRED_PAGE_ID_FIELD] === 'string' && input[REQUIRED_PAGE_ID_FIELD].length > 0\n ? input[REQUIRED_PAGE_ID_FIELD]\n : undefined;\n const requestedBrowserId =\n typeof input[OPTIONAL_BROWSER_ID_FIELD] === 'string' && input[OPTIONAL_BROWSER_ID_FIELD].length > 0\n ? input[OPTIONAL_BROWSER_ID_FIELD]\n : undefined;\n\n if (!requestedPageId && !requestedBrowserId) {\n throw new Error(`Custom tool \"${toolName}\" requires a string pageId or browserId`);\n }\n\n if (requestedPageId) {\n const pageEntry = this.pageRegistry.get(requestedPageId);\n if (!pageEntry) {\n throw new Error(`Page \"${requestedPageId}\" not found`);\n }\n if (requestedBrowserId && pageEntry.browserId !== requestedBrowserId) {\n throw new Error(\n `Custom tool \"${toolName}\" received pageId \"${requestedPageId}\" for browser \"${pageEntry.browserId}\", not \"${requestedBrowserId}\"`,\n );\n }\n\n const browser = this.createBrowserHelper(toolName, pageEntry.browserId);\n return {\n pageId: requestedPageId,\n pageEntry,\n page: this.resolveToolPage(toolName, requestedPageId, pageEntry),\n browser,\n };\n }\n\n const browser = this.createBrowserHelper(toolName, requestedBrowserId as string);\n try {\n const pageHandle = await browser.getCurrentPage();\n const pageEntry = this.pageRegistry.get(pageHandle.pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageHandle.pageId}\" not found`);\n }\n\n return {\n pageId: pageHandle.pageId,\n pageEntry,\n page: pageHandle.page,\n browser,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (!message.includes('has no pages')) {\n throw error;\n }\n\n const pageHandle = await browser.newPage();\n const pageEntry = this.pageRegistry.get(pageHandle.pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageHandle.pageId}\" not found`, {\n cause: error,\n });\n }\n\n return {\n pageId: pageHandle.pageId,\n pageEntry,\n page: pageHandle.page,\n browser,\n };\n }\n }\n\n private createBrowserHelper(toolName: string, browserId: string): CustomToolBrowser {\n const listPages = async (): Promise<CustomToolBrowserPageSummary[]> => {\n const browserInstance = this.browserService?.getBrowser(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n return this.pageRegistry\n .findByBrowser(browserId)\n .map((entry) => this.toPageSummary(browserInstance.currentPageId, entry));\n };\n\n return {\n browserId,\n mode: this.browserService?.getBrowser(browserId)?.mode ?? 'extension',\n listPages,\n getPage: async (pageId: string): Promise<CustomToolBrowserPageHandle> => {\n const pageEntry = this.pageRegistry.get(pageId);\n if (!pageEntry || pageEntry.browserId !== browserId) {\n throw new Error(`Page \"${pageId}\" not found in browser \"${browserId}\"`);\n }\n\n const browserInstance = this.browserService?.getBrowser(browserId);\n return {\n ...this.toPageSummary(browserInstance?.currentPageId ?? null, pageEntry),\n page: this.resolveToolPage(toolName, pageId, pageEntry),\n };\n },\n getCurrentPage: async (): Promise<CustomToolBrowserPageHandle> => {\n const browserInstance = this.browserService?.getBrowser(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n const currentPageId =\n browserInstance.currentPageId ?? this.pageRegistry.findByBrowser(browserId)[0]?.id ?? undefined;\n if (!currentPageId) {\n throw new Error(`Browser \"${browserId}\" has no pages`);\n }\n\n const pageEntry = this.pageRegistry.get(currentPageId);\n if (!pageEntry) {\n throw new Error(`Page \"${currentPageId}\" not found`);\n }\n\n return {\n ...this.toPageSummary(browserInstance.currentPageId, pageEntry),\n page: this.resolveToolPage(toolName, currentPageId, pageEntry),\n };\n },\n newPage: async (options?: { url?: string; setAsCurrent?: boolean }): Promise<CustomToolBrowserPageHandle> =>\n this.createPageForBrowser(toolName, browserId, options),\n };\n }\n\n private createToolLogger(toolName: string, pageId: string, pageEntry: PageEntry): CustomToolLogger {\n const baseAttributes: Record<string, unknown> = {\n 'browse_tool.tool.name': toolName,\n 'browse_tool.page.id': pageId,\n 'browse_tool.browser.id': pageEntry.browserId,\n 'browse_tool.execution.mode': pageEntry.mode,\n };\n\n const emit = (\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal',\n message: string,\n options?: CustomToolLogOptions,\n ): void => {\n this.telemetry.log(level, message, {\n attributes: toAttributes({\n ...baseAttributes,\n ...(options?.attributes ?? {}),\n }),\n exception: options?.exception,\n });\n };\n\n return {\n getTraceContext: () => this.telemetry.getActiveTraceContext(),\n trace: (message, options) => emit('trace', message, options),\n debug: (message, options) => emit('debug', message, options),\n info: (message, options) => emit('info', message, options),\n warn: (message, options) => emit('warn', message, options),\n error: (message, options) => emit('error', message, options),\n fatal: (message, options) => emit('fatal', message, options),\n };\n }\n\n async listTools(directory: string): Promise<CustomToolDefinition[]> {\n const tools = await this.loadTools(directory);\n return tools.map(({ name, description, suggestionActions, inputSchema, capabilities }) => ({\n name,\n description,\n suggestionActions,\n inputSchema,\n capabilities,\n }));\n }\n\n async executeTool(directory: string, toolName: string, input: Record<string, unknown>): Promise<CallToolResult> {\n const requestedPageId =\n typeof input[REQUIRED_PAGE_ID_FIELD] === 'string' ? input[REQUIRED_PAGE_ID_FIELD] : undefined;\n const requestedBrowserId =\n typeof input[OPTIONAL_BROWSER_ID_FIELD] === 'string' ? input[OPTIONAL_BROWSER_ID_FIELD] : undefined;\n\n return this.telemetry.runInSpan(\n 'browse_tool.custom_tool.execute',\n {\n attributes: {\n 'browse_tool.tool.name': toolName,\n 'browse_tool.custom_tools.directory': path.resolve(directory),\n 'browse_tool.page.id': requestedPageId,\n 'browse_tool.browser.id': requestedBrowserId,\n },\n },\n async (span) => {\n const tools = await this.loadTools(directory);\n const tool = tools.find((candidate) => candidate.name === toolName);\n\n if (!tool) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: `Custom tool \"${toolName}\" not found` });\n throw new Error(`Custom tool \"${toolName}\" not found`);\n }\n\n const { pageId, pageEntry, page, browser } = await this.resolveExecutionContext(toolName, input);\n const logger = this.createToolLogger(toolName, pageId, pageEntry);\n span?.setAttributes({\n 'browse_tool.browser.id': pageEntry.browserId,\n 'browse_tool.execution.mode': pageEntry.mode,\n 'browse_tool.page.id': pageId,\n });\n debugCustomTool('Executing custom tool', {\n toolName,\n directory: path.resolve(directory),\n pageId,\n browserId: pageEntry.browserId,\n mode: pageEntry.mode,\n input: summarizeValue(input),\n scriptPath: tool.scriptPath,\n });\n\n let rawResult;\n try {\n rawResult = await tool.execute?.({ page, browser, input, logger });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n debugCustomTool('Custom tool execution failed', {\n toolName,\n pageId,\n browserId: pageEntry.browserId,\n mode: pageEntry.mode,\n error: message,\n stack: error instanceof Error ? error.stack : undefined,\n });\n throw new Error(`Custom tool \"${toolName}\" failed on page \"${pageId}\": ${message}`, {\n cause: error,\n });\n }\n\n debugCustomTool('Custom tool execution completed', {\n toolName,\n pageId,\n browserId: pageEntry.browserId,\n mode: pageEntry.mode,\n result: summarizeValue(rawResult),\n });\n\n const result = normalizeExecutionResult(toolName, rawResult, tool.suggestionActions);\n const errorMessage = result.isError ? (result.content[0] as { text?: string })?.text : undefined;\n if (errorMessage) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n }\n\n return result;\n },\n );\n }\n\n private async loadTools(directory: string): Promise<LoadedCustomTool[]> {\n const resolvedDirectory = path.resolve(directory);\n const manifestPath = path.join(resolvedDirectory, MANIFEST_FILE);\n const manifestSource = await readFile(manifestPath, 'utf8').catch((error) => {\n throw new Error(\n `Failed to read custom tool manifest at \"${manifestPath}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n });\n\n const parsedManifest = parseYamlDocument(manifestSource);\n const manifest = CustomToolManifestSchema.parse(parsedManifest);\n\n return Promise.all(\n manifest.tools.map(async (tool) => {\n ensureExecutionTargetSchema(tool);\n\n const scriptPath = path.resolve(resolvedDirectory, tool.script);\n await stat(scriptPath).catch((error) => {\n throw new Error(\n `Custom tool \"${tool.name}\" script not found at \"${scriptPath}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n });\n\n const loadedModule = await loadCustomToolModule(scriptPath);\n const executionHooks = validateCustomToolModule(scriptPath, loadedModule);\n\n return {\n name: tool.name,\n description: tool.description,\n suggestionActions: tool.suggestionActions,\n inputSchema: tool.inputSchema,\n capabilities: tool.capabilities,\n scriptPath,\n ...executionHooks,\n };\n }),\n );\n }\n}\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\n\n/**\n * Browser data for API response\n */\ninterface BrowserResponse {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: string;\n pages: PageResponse[];\n}\n\n/**\n * Page data for API response\n */\ninterface PageResponse {\n id: string;\n url: string;\n title: string;\n createdAt: string;\n}\n\n/**\n * Stats for API response\n */\ninterface StatsResponse {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * Create API router for dashboard\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with API endpoints\n */\nexport function createApiRouter(container: Container): Hono {\n const app = new Hono();\n\n /**\n * GET /api/browsers - List all browsers with nested pages and stats\n */\n app.get('/browsers', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const browserInstances = browserService.listBrowsers();\n const allPages = pageRegistry.list();\n\n // Build browser response with nested pages\n const browsers: BrowserResponse[] = browserInstances.map((browser: BrowserInstance) => {\n const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n return {\n id: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n createdAt: browser.createdAt.toISOString(),\n pages: browserPages.map((page: PageEntry) => ({\n id: page.id,\n url: page.url,\n title: page.title,\n createdAt: page.createdAt.toISOString(),\n })),\n };\n });\n\n const stats: StatsResponse = {\n totalBrowsers: browserInstances.length,\n totalPages: allPages.length,\n };\n\n return c.json({ browsers, stats });\n } catch (error) {\n console.error('Failed to list browsers:', error);\n return c.json(\n {\n error: 'Failed to list browsers',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/browsers - Close all browsers\n */\n app.delete('/browsers', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n await browserService.closeAll();\n\n return c.json({ success: true, message: 'All browsers closed' });\n } catch (error) {\n console.error('Failed to close all browsers:', error);\n return c.json(\n {\n error: 'Failed to close all browsers',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/browsers/:id - Close a specific browser\n */\n app.delete('/browsers/:id', async (c) => {\n try {\n const browserId = c.req.param('id');\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n\n const browser = browserService.getBrowser(browserId);\n if (!browser) {\n return c.json({ error: `Browser \"${browserId}\" not found` }, 404);\n }\n\n await browserService.closeBrowser(browserId);\n\n return c.json({ success: true, message: `Browser \"${browserId}\" closed` });\n } catch (error) {\n console.error('Failed to close browser:', error);\n return c.json(\n {\n error: 'Failed to close browser',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/pages/:id - Close a specific page\n */\n app.delete('/pages/:id', async (c) => {\n try {\n const pageId = c.req.param('id');\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const pageEntry = pageRegistry.get(pageId);\n if (!pageEntry) {\n return c.json({ error: `Page \"${pageId}\" not found` }, 404);\n }\n\n if (!pageEntry.page) {\n return c.json({ error: `Page \"${pageId}\" is in extension mode and cannot be closed via API` }, 400);\n }\n\n // Close the page - this will trigger the registry cleanup via the 'close' event\n await pageEntry.page.close();\n\n return c.json({ success: true, message: `Page \"${pageId}\" closed` });\n } catch (error) {\n console.error('Failed to close page:', error);\n return c.json(\n {\n error: 'Failed to close page',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n return app;\n}\n","/**\n * Utility formatters for dashboard display\n */\n\n/**\n * Format a Date object to a human-readable string\n *\n * @param date - Date to format\n * @returns Formatted timestamp string\n */\nexport function formatTimestamp(date: Date): string {\n return date.toLocaleString('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n}\n\n/**\n * Format duration in milliseconds to human readable\n *\n * @param ms - Duration in milliseconds\n * @returns Formatted duration string\n */\nexport function formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Calculate duration since a given date\n *\n * @param date - Start date\n * @returns Duration string\n */\nexport function formatAge(date: Date): string {\n const now = new Date();\n const ms = now.getTime() - date.getTime();\n return formatDuration(ms);\n}\n","// Plain CSS class names for SSR compatibility\nexport const container = 'dashboard-container';\nexport const header = 'dashboard-header';\nexport const controlsSection = 'controls-section';\nexport const button = 'btn';\nexport const buttonDanger = 'btn-danger';\nexport const tableContainer = 'table-container';\nexport const table = 'browser-table';\nexport const statsContainer = 'stats-container';\nexport const statCard = 'stat-card';\nexport const browserRow = 'browser-row';\nexport const pageRow = 'page-row';\nexport const statusActive = 'status-active';\nexport const statusInactive = 'status-inactive';\nexport const urlCell = 'url-cell';\nexport const timestampCell = 'timestamp-cell';\nexport const actionsCell = 'actions-cell';\nexport const killButton = 'kill-btn';\nexport const refreshButton = 'refresh-btn';\nexport const emptyState = 'empty-state';\n\n// Global styles as plain CSS string for SSR\nexport const globalStyles = `\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #333;\n background-color: #f5f5f5;\n }\n\n h1, h2, h3 {\n margin-bottom: 1rem;\n }\n\n .dashboard-container {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n }\n\n .dashboard-header {\n background-color: #fff;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .dashboard-header h1 {\n margin-bottom: 0;\n }\n\n .controls-section {\n background-color: #fff;\n padding: 1rem;\n margin-bottom: 1rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n display: flex;\n gap: 1rem;\n align-items: center;\n flex-wrap: wrap;\n }\n\n .btn {\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n font-size: 14px;\n cursor: pointer;\n background-color: #2196f3;\n color: white;\n transition: background-color 0.2s;\n }\n\n .btn:hover {\n background-color: #1976d2;\n }\n\n .btn:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n }\n\n .btn-danger {\n background-color: #f44336;\n }\n\n .btn-danger:hover {\n background-color: #d32f2f;\n }\n\n .table-container {\n background-color: #fff;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n }\n\n .browser-table {\n width: 100%;\n border-collapse: collapse;\n table-layout: fixed;\n }\n\n .browser-table th {\n background-color: #f8f9fa;\n padding: 0.75rem;\n text-align: left;\n font-weight: 600;\n border-bottom: 2px solid #dee2e6;\n position: sticky;\n top: 0;\n }\n\n .browser-table th:nth-child(1) {\n width: 12%;\n }\n\n .browser-table th:nth-child(2) {\n width: 10%;\n }\n\n .browser-table th:nth-child(3) {\n width: 38%;\n }\n\n .browser-table th:nth-child(4) {\n width: 15%;\n }\n\n .browser-table th:nth-child(5) {\n width: 15%;\n }\n\n .browser-table th:nth-child(6) {\n width: 10%;\n }\n\n .browser-table td {\n padding: 0.75rem;\n border-bottom: 1px solid #dee2e6;\n vertical-align: middle;\n }\n\n .browser-row {\n background-color: #e3f2fd;\n font-weight: 500;\n }\n\n .browser-row:hover {\n background-color: #bbdefb;\n }\n\n .page-row {\n background-color: #fff;\n }\n\n .page-row:hover {\n background-color: #f8f9fa;\n }\n\n .page-row td:first-child {\n padding-left: 2rem;\n }\n\n .status-active {\n color: #4caf50;\n font-weight: 600;\n }\n\n .status-inactive {\n color: #9e9e9e;\n }\n\n .url-cell {\n max-width: 400px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: 'Courier New', monospace;\n font-size: 13px;\n }\n\n .timestamp-cell {\n white-space: nowrap;\n font-size: 12px;\n color: #666;\n }\n\n .actions-cell {\n text-align: center;\n }\n\n .kill-btn {\n padding: 0.25rem 0.5rem;\n font-size: 12px;\n background-color: #ff5722;\n border: none;\n border-radius: 4px;\n color: white;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n .kill-btn:hover {\n background-color: #e64a19;\n }\n\n .refresh-btn {\n background-color: #4caf50;\n }\n\n .refresh-btn:hover {\n background-color: #388e3c;\n }\n\n .stats-container {\n display: flex;\n gap: 1.5rem;\n margin-bottom: 1rem;\n }\n\n .stat-card {\n background-color: #fff;\n padding: 1rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n flex: 1;\n }\n\n .stat-card h3 {\n font-size: 0.875rem;\n color: #666;\n margin-bottom: 0.5rem;\n }\n\n .stat-card .value {\n font-size: 1.5rem;\n font-weight: 700;\n color: #333;\n }\n\n .empty-state {\n padding: 3rem;\n text-align: center;\n color: #666;\n }\n\n .empty-state h3 {\n margin-bottom: 0.5rem;\n color: #999;\n }\n`;\n","import { formatAge, formatTimestamp } from '../utils/formatters.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * BrowserTable component props\n */\ninterface BrowserTableProps {\n browsers: BrowserData[];\n}\n\n/**\n * BrowserTable component\n *\n * Displays browsers and their pages in a hierarchical table\n */\nexport function BrowserTable({ browsers }: BrowserTableProps) {\n if (browsers.length === 0) {\n return (\n <div class={styles.tableContainer}>\n <div class={styles.emptyState}>\n <h3>No Active Browsers</h3>\n <p>Launch a browser using MCP tools to see it here.</p>\n </div>\n </div>\n );\n }\n\n return (\n <div class={styles.tableContainer}>\n <table class={styles.table}>\n <thead>\n <tr>\n <th>ID</th>\n <th>Status</th>\n <th>URL / Title</th>\n <th>Created</th>\n <th>Age</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody id=\"browser-table-body\">\n {browsers.map((browser) => (\n <>\n <tr class={styles.browserRow}>\n <td>{browser.id}</td>\n <td>\n <span class={styles.statusActive}>\n {browser.pages.length} page{browser.pages.length !== 1 ? 's' : ''}\n </span>\n </td>\n <td>{browser.profileName || 'Default Profile'}</td>\n <td class={styles.timestampCell}>{formatTimestamp(browser.createdAt)}</td>\n <td class={styles.timestampCell}>{formatAge(browser.createdAt)}</td>\n <td class={styles.actionsCell}>\n <button class={styles.killButton} onclick={`dashboard.killBrowser('${browser.id}')`}>\n Kill\n </button>\n </td>\n </tr>\n {browser.pages.map((page) => (\n <tr class={styles.pageRow}>\n <td>\n {page.id}\n {browser.currentPageId === page.id && ' (active)'}\n </td>\n <td>\n <span class={styles.statusActive}>Open</span>\n </td>\n <td class={styles.urlCell} title={page.url}>\n {page.title || page.url || 'about:blank'}\n </td>\n <td class={styles.timestampCell}>{formatTimestamp(page.createdAt)}</td>\n <td class={styles.timestampCell}>{formatAge(page.createdAt)}</td>\n <td class={styles.actionsCell}>\n <button class={styles.killButton} onclick={`dashboard.killPage('${page.id}')`}>\n Close\n </button>\n </td>\n </tr>\n ))}\n </>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n","import * as styles from './styles.js';\n\n/**\n * Stats data structure\n */\ninterface Stats {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * StatsHeader component props\n */\ninterface StatsHeaderProps {\n stats: Stats;\n}\n\n/**\n * StatsHeader component\n *\n * Displays browser and page statistics\n */\nexport function StatsHeader({ stats }: StatsHeaderProps) {\n return (\n <div class={styles.statsContainer}>\n <div class={styles.statCard}>\n <h3>Active Browsers</h3>\n <div class=\"value\">{stats.totalBrowsers}</div>\n </div>\n <div class={styles.statCard}>\n <h3>Active Pages</h3>\n <div class=\"value\">{stats.totalPages}</div>\n </div>\n </div>\n );\n}\n","import { raw } from 'hono/html';\nimport { BrowserTable } from './BrowserTable.js';\nimport { StatsHeader } from './StatsHeader.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * Stats data structure\n */\ninterface Stats {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * Dashboard component props\n */\ninterface DashboardProps {\n browsers: BrowserData[];\n stats: Stats;\n}\n\n/**\n * Dashboard component\n *\n * Main dashboard UI with auto-refresh polling and browser/page management\n */\nexport function Dashboard({ browsers, stats }: DashboardProps) {\n return (\n <div class={styles.container}>\n <div class={styles.header}>\n <div>\n <h1>Playwright MCP Dashboard</h1>\n <p>Browser automation management (auto-refresh every 3 seconds)</p>\n </div>\n <div>\n <button\n id=\"refresh-btn\"\n class={`${styles.button} ${styles.refreshButton}`}\n onclick=\"dashboard.manualRefresh()\"\n >\n Refresh Now\n </button>\n <button\n id=\"kill-all-btn\"\n class={`${styles.button} ${styles.buttonDanger}`}\n onclick=\"dashboard.killAllBrowsers()\"\n >\n Kill All Browsers\n </button>\n </div>\n </div>\n\n <StatsHeader stats={stats} />\n\n <BrowserTable browsers={browsers} />\n\n <script>\n {raw(`\nclass DashboardManager {\n constructor() {\n this.autoRefreshInterval = null;\n this.isRefreshing = false;\n this.startAutoRefresh();\n }\n\n startAutoRefresh() {\n this.autoRefreshInterval = setInterval(() => {\n this.fetchBrowsers();\n }, 3000);\n }\n\n stopAutoRefresh() {\n if (this.autoRefreshInterval) {\n clearInterval(this.autoRefreshInterval);\n this.autoRefreshInterval = null;\n }\n }\n\n async fetchBrowsers() {\n if (this.isRefreshing) return;\n this.isRefreshing = true;\n\n try {\n const response = await fetch('/api/browsers');\n const data = await response.json();\n this.updateStats(data.stats);\n this.updateBrowserTable(data.browsers);\n } catch (error) {\n console.error('Failed to fetch browsers:', error);\n } finally {\n this.isRefreshing = false;\n }\n }\n\n updateStats(stats) {\n const cards = document.querySelectorAll('.${styles.statCard} .value');\n if (cards.length >= 2) {\n cards[0].textContent = stats.totalBrowsers;\n cards[1].textContent = stats.totalPages;\n }\n }\n\n updateBrowserTable(browsers) {\n const tbody = document.getElementById('browser-table-body');\n if (!tbody) return;\n\n if (browsers.length === 0) {\n const container = tbody.closest('.${styles.tableContainer}');\n if (container) {\n container.innerHTML = \\`\n <div class=\"${styles.emptyState}\">\n <h3>No Active Browsers</h3>\n <p>Launch a browser using MCP tools to see it here.</p>\n </div>\n \\`;\n }\n return;\n }\n\n // Rebuild table content\n tbody.innerHTML = '';\n browsers.forEach(browser => {\n // Browser row\n const browserRow = document.createElement('tr');\n browserRow.className = '${styles.browserRow}';\n browserRow.innerHTML = \\`\n <td>\\${browser.id}</td>\n <td><span class=\"${styles.statusActive}\">\\${browser.pages.length} page\\${browser.pages.length !== 1 ? 's' : ''}</span></td>\n <td>\\${browser.profileName || 'Default Profile'}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatTimestamp(browser.createdAt)}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatAge(browser.createdAt)}</td>\n <td class=\"${styles.actionsCell}\"><button class=\"${styles.killButton}\" onclick=\"dashboard.killBrowser('\\${browser.id}')\">Kill</button></td>\n \\`;\n tbody.appendChild(browserRow);\n\n // Page rows\n browser.pages.forEach(page => {\n const pageRow = document.createElement('tr');\n pageRow.className = '${styles.pageRow}';\n const isActive = browser.currentPageId === page.id;\n pageRow.innerHTML = \\`\n <td>\\${page.id}\\${isActive ? ' (active)' : ''}</td>\n <td><span class=\"${styles.statusActive}\">Open</span></td>\n <td class=\"${styles.urlCell}\" title=\"\\${this.escapeHtml(page.url)}\">\\${this.escapeHtml(page.title || page.url || 'about:blank')}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatTimestamp(page.createdAt)}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatAge(page.createdAt)}</td>\n <td class=\"${styles.actionsCell}\"><button class=\"${styles.killButton}\" onclick=\"dashboard.killPage('\\${page.id}')\">Close</button></td>\n \\`;\n tbody.appendChild(pageRow);\n });\n });\n }\n\n formatTimestamp(timestamp) {\n const date = new Date(timestamp);\n return date.toLocaleString('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n }\n\n formatAge(timestamp) {\n const now = new Date();\n const date = new Date(timestamp);\n const ms = now.getTime() - date.getTime();\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) return \\`\\${hours}h \\${minutes % 60}m\\`;\n if (minutes > 0) return \\`\\${minutes}m \\${seconds % 60}s\\`;\n return \\`\\${seconds}s\\`;\n }\n\n escapeHtml(str) {\n if (typeof str !== 'string') return str;\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n manualRefresh() {\n this.fetchBrowsers();\n }\n\n async killBrowser(browserId) {\n try {\n const response = await fetch(\\`/api/browsers/\\${browserId}\\`, { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to kill browser:', await response.text());\n }\n } catch (error) {\n console.error('Failed to kill browser:', error);\n }\n }\n\n async killPage(pageId) {\n try {\n const response = await fetch(\\`/api/pages/\\${pageId}\\`, { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to close page:', await response.text());\n }\n } catch (error) {\n console.error('Failed to close page:', error);\n }\n }\n\n async killAllBrowsers() {\n if (!confirm('Are you sure you want to close all browsers?')) return;\n\n try {\n const response = await fetch('/api/browsers', { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to kill all browsers:', await response.text());\n }\n } catch (error) {\n console.error('Failed to kill all browsers:', error);\n }\n }\n}\n\n// Initialize dashboard\nconst dashboard = new DashboardManager();\n\n// Cleanup on page unload\nwindow.addEventListener('beforeunload', () => {\n dashboard.stopAutoRefresh();\n});\n `)}\n </script>\n </div>\n );\n}\n","import { raw } from 'hono/html';\nimport { globalStyles } from './styles.js';\n\n/**\n * Layout component props\n */\ninterface LayoutProps {\n title: string;\n children: unknown;\n}\n\n/**\n * Layout component\n *\n * Provides the HTML wrapper with styles and meta tags for SSR\n */\nexport function Layout({ title, children }: LayoutProps) {\n return (\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{title}</title>\n <style>{raw(globalStyles)}</style>\n </head>\n <body>{children}</body>\n </html>\n );\n}\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\nimport { Dashboard } from '../components/Dashboard.js';\nimport { Layout } from '../components/Layout.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * Create dashboard router for server-side rendered UI\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with dashboard routes\n */\nexport function createDashboardRouter(container: Container): Hono {\n const app = new Hono();\n\n /**\n * GET / - Dashboard home page with SSR\n *\n * Renders the dashboard UI with initial server-side data\n */\n app.get('/', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const browserInstances = browserService.listBrowsers();\n const allPages = pageRegistry.list();\n\n // Build browser data with nested pages\n const browsers: BrowserData[] = browserInstances.map((browser: BrowserInstance) => {\n const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n return {\n id: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n createdAt: browser.createdAt,\n pages: browserPages.map((page: PageEntry) => ({\n id: page.id,\n url: page.url,\n title: page.title,\n createdAt: page.createdAt,\n })),\n };\n });\n\n const stats = {\n totalBrowsers: browserInstances.length,\n totalPages: allPages.length,\n };\n\n // Render dashboard with Layout wrapper\n return c.html(\n <Layout title=\"Playwright MCP Dashboard\">\n <Dashboard browsers={browsers} stats={stats} />\n </Layout>,\n );\n } catch (error) {\n console.error('Failed to render dashboard:', error);\n\n return c.html(\n <Layout title=\"Playwright MCP Dashboard - Error\">\n <div style=\"padding: 2rem; text-align: center;\">\n <h1>Failed to load dashboard</h1>\n <p>{error instanceof Error ? error.message : String(error)}</p>\n <a href=\"/\" style=\"color: #2196f3; text-decoration: underline;\">\n Retry\n </a>\n </div>\n </Layout>,\n 500,\n );\n }\n });\n\n return app;\n}\n","/**\n * HTTP Server for Browser Automation\n *\n * DESIGN PATTERNS:\n * - Hono for HTTP routing and middleware\n * - Dependency injection with InversifyJS Container\n * - Service layer for business logic\n * - Generic execute endpoint for all tools\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing and middleware\n * - Use CORS middleware for cross-origin requests\n * - Keep route handlers thin, delegate to services\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../services/BrowserService.js';\nimport { CustomToolService } from '../services/CustomToolService.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { TelemetryService, type ITelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.js';\nimport { createApiRouter } from './dashboard/routes/api.js';\nimport { createDashboardRouter } from './dashboard/routes/dashboard.js';\nimport { createExtensionRoutes } from './extension-routes.js';\n\n/**\n * Execute request body\n */\ninterface ExecuteRequest {\n tool: string;\n arguments: Record<string, unknown>;\n}\n\n/**\n * Execute response body\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: CallToolResult;\n error?: string;\n}\n\ninterface CustomToolsResponse {\n tools: Array<{\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: Record<string, unknown>;\n capabilities: Record<string, unknown>;\n }>;\n error?: string;\n}\n\n/**\n * Browser info for health response\n */\ninterface BrowserInfo {\n id: string;\n pageCount: number;\n createdAt: string;\n}\n\n/**\n * Health response\n */\ninterface HealthResponse {\n status: 'healthy' | 'unhealthy';\n service: string;\n serviceName: string;\n timestamp: string;\n browsers: {\n count: number;\n instances: BrowserInfo[];\n };\n}\n\nfunction getResultError(result: CallToolResult): string | undefined {\n if (!result.isError) {\n return undefined;\n }\n\n const firstContent = result.content[0];\n if (firstContent?.type === 'text' && typeof firstContent.text === 'string') {\n return firstContent.text;\n }\n\n return 'Unknown tool execution error';\n}\n\nfunction resolveTelemetryService(container: Container): ITelemetryService {\n try {\n return container.get<ITelemetryService>(PLAYWRIGHT_TYPES.TelemetryService);\n } catch {\n return new TelemetryService();\n }\n}\n\n/**\n * Create HTTP server for browser automation\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono app\n */\nexport function createHttpServer(container: Container): Hono {\n const app = new Hono();\n const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n PLAYWRIGHT_TYPES.PageRegistry,\n );\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const extensionTaskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const telemetry = resolveTelemetryService(container);\n const customToolService = new CustomToolService(pageRegistry, extensionTaskQueue, telemetry, browserService);\n\n // CORS middleware - allow all origins\n app.use(\n '*',\n cors({\n origin: (origin) => origin ?? null,\n credentials: true,\n allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Accept', 'Authorization'],\n }),\n );\n\n // Health check endpoint\n app.get('/health', (c) => {\n try {\n const browsers = browserService.listBrowsers();\n\n const response: HealthResponse = {\n status: 'healthy',\n service: 'browse-tool-http',\n serviceName: 'browse-tool-http',\n timestamp: new Date().toISOString(),\n browsers: {\n count: browsers.length,\n instances: browsers.map((b: BrowserInstance) => ({\n id: b.id,\n pageCount: b.pageIds.size,\n createdAt: b.createdAt.toISOString(),\n })),\n },\n };\n\n return c.json(response);\n } catch (error) {\n return c.json(\n {\n status: 'unhealthy',\n service: 'browse-tool-http',\n serviceName: 'browse-tool-http',\n error: error instanceof Error ? error.message : String(error),\n },\n 503,\n );\n }\n });\n\n // Execute tool endpoint\n app.post('/execute', async (c) => {\n try {\n const body = (await c.req.json()) as ExecuteRequest;\n\n if (!body.tool) {\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: 'Missing \"tool\" field in request body',\n },\n 400,\n );\n }\n\n const pageId = typeof body.arguments?.pageId === 'string' ? body.arguments.pageId : undefined;\n\n return await telemetry.runInSpan(\n 'browse_tool.http.execute',\n {\n attributes: {\n 'http.method': 'POST',\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n },\n },\n async (span) => {\n const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n const tool = tools.find((t) => t.getDefinition().name === body.tool);\n\n if (process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1' && body.tool === 'browser_launch') {\n const videoDir =\n typeof body.arguments?.videoDir === 'string'\n ? body.arguments.videoDir\n : typeof body.arguments?.video_dir === 'string'\n ? body.arguments.video_dir\n : '';\n console.log(\n `[ExtensionRecordingDebug] http execute browser_launch mode=${String(body.arguments?.mode ?? '')} videoDir=${videoDir}`,\n );\n }\n\n if (!tool) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: `Tool \"${body.tool}\" not found` });\n telemetry.log('warn', 'browse-tool HTTP execute rejected unknown tool', {\n attributes: {\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n },\n });\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: `Tool \"${body.tool}\" not found`,\n },\n 404,\n );\n }\n\n const result = await tool.execute(body.arguments || {});\n const args = body.arguments || {};\n const requestPageId = typeof args.pageId === 'string' ? args.pageId : undefined;\n if (requestPageId) {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageEntry = pageRegistry.get(requestPageId);\n if (pageEntry) {\n browserService.recordBrowserActivity(pageEntry.browserId, requestPageId);\n }\n }\n\n const errorMessage = getResultError(result);\n if (errorMessage) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n telemetry.log('error', 'browse-tool HTTP execute failed', {\n attributes: {\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': requestPageId,\n },\n });\n } else {\n telemetry.log('info', 'browse-tool HTTP execute succeeded', {\n attributes: {\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': requestPageId,\n },\n });\n }\n\n return c.json<ExecuteResponse>({\n success: !result.isError,\n result,\n error: errorMessage,\n });\n },\n );\n } catch (error) {\n telemetry.log('error', 'browse-tool HTTP execute request crashed', {\n attributes: {\n 'http.route': '/execute',\n },\n exception: error,\n });\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // List browsers endpoint\n app.get('/browsers', (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const browsers = browserService.listBrowsers();\n\n return c.json({\n browsers: browsers.map((b: BrowserInstance) => ({\n id: b.id,\n profileName: b.profileName,\n pageIds: Array.from(b.pageIds),\n currentPageId: b.currentPageId,\n createdAt: b.createdAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // List tools endpoint\n app.get('/tools', (c) => {\n try {\n const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n return c.json({\n tools: tools.map((t) => t.getDefinition()),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n app.get('/custom-tools', async (c) => {\n const directory = c.req.query('dir');\n\n if (!directory) {\n return c.json<CustomToolsResponse>({ tools: [], error: 'Missing \"dir\" query parameter' }, 400);\n }\n\n try {\n const tools = await customToolService.listTools(directory);\n return c.json<CustomToolsResponse>({ tools });\n } catch (error) {\n return c.json<CustomToolsResponse>(\n {\n tools: [],\n error: error instanceof Error ? error.message : String(error),\n },\n 400,\n );\n }\n });\n\n app.post('/custom-tools', async (c) => {\n const directory = c.req.query('dir');\n\n if (!directory) {\n return c.json<ExecuteResponse>({ success: false, error: 'Missing \"dir\" query parameter' }, 400);\n }\n\n try {\n const body = (await c.req.json()) as ExecuteRequest;\n\n if (!body.tool) {\n return c.json<ExecuteResponse>({ success: false, error: 'Missing \"tool\" field in request body' }, 400);\n }\n\n const pageId = typeof body.arguments?.pageId === 'string' ? body.arguments.pageId : undefined;\n\n return await telemetry.runInSpan(\n 'browse_tool.http.custom_tool.execute',\n {\n attributes: {\n 'http.method': 'POST',\n 'http.route': '/custom-tools',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n 'browse_tool.custom_tools.directory': directory,\n },\n },\n async (span) => {\n const result = await customToolService.executeTool(directory, body.tool, body.arguments || {});\n if (pageId) {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageEntry = pageRegistry.get(pageId);\n if (pageEntry) {\n browserService.recordBrowserActivity(pageEntry.browserId, pageId);\n }\n }\n\n const errorMessage = getResultError(result);\n if (errorMessage) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n telemetry.log('error', 'browse-tool custom tool execution failed', {\n attributes: {\n 'http.route': '/custom-tools',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n },\n });\n } else {\n telemetry.log('info', 'browse-tool custom tool execution succeeded', {\n attributes: {\n 'http.route': '/custom-tools',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n },\n });\n }\n\n return c.json<ExecuteResponse>({\n success: !result.isError,\n result,\n error: errorMessage,\n });\n },\n );\n } catch (error) {\n telemetry.log('error', 'browse-tool custom tool request crashed', {\n attributes: {\n 'http.route': '/custom-tools',\n 'browse_tool.custom_tools.directory': directory,\n },\n exception: error,\n });\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // Mount extension routes for Chrome extension communication\n const extensionRouter = createExtensionRoutes(container);\n app.route('/extension', extensionRouter);\n\n // Mount dashboard API routes\n const apiRouter = createApiRouter(container);\n app.route('/api', apiRouter);\n\n // Mount dashboard UI routes (SSR)\n const dashboardRouter = createDashboardRouter(container);\n app.route('/', dashboardRouter);\n\n return app;\n}\n","/**\n * WebSocket Routes for Extension Communication\n *\n * DESIGN PATTERNS:\n * - WebSocket upgrade handler using @hono/node-ws\n * - Dependency injection with InversifyJS Container\n * - Pub/sub pattern via WebSocketHub\n *\n * CODING STANDARDS:\n * - Use Hono WebSocket helpers for upgrade\n * - Keep route handlers thin, delegate to WebSocketHub\n * - Handle connection lifecycle properly\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n * - Memory leaks from uncleaned connections\n */\n\nimport type { Hono } from 'hono';\nimport type { UpgradeWebSocket } from 'hono/ws';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\n\n/**\n * Setup WebSocket routes for extension communication\n *\n * @param app - Hono app instance\n * @param container - InversifyJS container with services\n * @param upgradeWebSocket - WebSocket upgrade function from @hono/node-ws\n */\nexport function setupWebSocketRoutes(app: Hono, container: Container, upgradeWebSocket: UpgradeWebSocket): void {\n /**\n * WebSocket endpoint for extension connections\n * Path: /ws/extension/:browserId\n */\n app.get(\n '/ws/extension/:browserId',\n upgradeWebSocket((c) => {\n const browserId = c.req.param('browserId');\n let connectionId: string | null = null;\n\n return {\n onOpen(_event, ws) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n connectionId = wsHub.addConnection(ws, browserId);\n } catch {\n ws.close(1011, 'Internal server error');\n }\n },\n\n onMessage(event) {\n if (!connectionId) {\n return;\n }\n\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const data = typeof event.data === 'string' ? event.data : event.data.toString();\n wsHub.handleMessage(connectionId, data);\n } catch {\n // Error handling delegated to WebSocketHub\n }\n },\n\n onClose() {\n if (connectionId) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n wsHub.removeConnection(connectionId);\n } catch {\n // Ignore cleanup errors\n }\n }\n },\n\n onError() {\n if (connectionId) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n wsHub.removeConnection(connectionId);\n } catch {\n // Ignore cleanup errors\n }\n }\n },\n };\n }),\n );\n\n /**\n * GET /ws/stats\n * Get WebSocket connection statistics\n */\n app.get('/ws/stats', (c) => {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const stats = wsHub.getStats();\n\n return c.json({\n totalConnections: stats.totalConnections,\n browserCount: stats.browserCount,\n connections: stats.connections.map((conn) => ({\n id: conn.id,\n browserId: conn.browserId,\n connectedAt: conn.connectedAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n}\n","/**\n * HTTP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n * - Dependency injection with InversifyJS Container\n * - Graceful shutdown pattern for SIGINT/SIGTERM\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n * - Implement graceful shutdown for server cleanup\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n * - Missing signal handlers for graceful shutdown\n */\n\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createHttpContainer } from '../container/index.js';\nimport { createHttpServer } from '../server/http.js';\nimport { setupWebSocketRoutes } from '../server/websocket-routes.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IExtensionSessionRegistry } from '../services/ExtensionSessionRegistry.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR, type IIdleCleanupService } from '../services/IdleCleanupService.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\nimport { DEFAULT_MCP_PORT, buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\ninterface HttpServeOptions {\n port: string;\n headless: boolean;\n idleTimeout: string;\n host: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n profilesDir?: string;\n snippetsDir?: string;\n}\n\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\n\nfunction resolveWorkspaceRoot(startPath = process.cwd()): string {\n let current = path.resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (existsSync(path.join(current, marker))) {\n return current;\n }\n }\n\n const parent = path.dirname(current);\n if (parent === current) {\n return process.cwd();\n }\n current = parent;\n }\n}\n\n/**\n * Start HTTP server for browser automation\n */\nexport const httpServeCommand = new Command('http-serve')\n .description('Start HTTP server for browser automation')\n .option('-p, --port <port>', 'Port to listen on', String(DEFAULT_MCP_PORT))\n .option('--host <host>', 'Host to bind', getPlaywrightHost())\n .option('--headless', 'Run browsers in headless mode by default', true)\n .option('--idle-timeout <minutes>', 'Idle timeout in minutes before closing browsers', '30')\n .option('--registry-dir <path>', 'Custom registry path or directory for service discovery')\n .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n .option('--pids-dir <path>', 'Custom PIDs directory for process tracking (deprecated)')\n .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n .option('--snippets-dir <path>', 'Directory used by browser_run_code to save and load reusable snippets')\n .action(async function (this: Command, options: HttpServeOptions) {\n const commandDefaults = getCommandConfig<{\n port?: number;\n headless?: boolean;\n idleTimeout?: number;\n host?: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n profilesDir?: string;\n snippetsDir?: string;\n }>('httpServe');\n const resolvedOptions: HttpServeOptions = {\n port: resolveConfiguredOption(\n this,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n ),\n headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n idleTimeout: resolveConfiguredOption(\n this,\n 'idleTimeout',\n options.idleTimeout,\n commandDefaults.idleTimeout !== undefined ? String(commandDefaults.idleTimeout) : undefined,\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR],\n ),\n host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n registryDir: resolveConfiguredOption(\n this,\n 'registryDir',\n options.registryDir,\n commandDefaults.registryDir,\n process.env.PLAYWRIGHT_REGISTRY_DIR,\n ),\n registryPath: resolveConfiguredOption(\n this,\n 'registryPath',\n options.registryPath,\n commandDefaults.registryPath,\n process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n ),\n pidsDir: resolveConfiguredOption(\n this,\n 'pidsDir',\n options.pidsDir,\n commandDefaults.pidsDir,\n process.env.PLAYWRIGHT_PIDS_DIR,\n ),\n profilesDir: resolveConfiguredOption(\n this,\n 'profilesDir',\n options.profilesDir,\n commandDefaults.profilesDir,\n process.env.PLAYWRIGHT_PROFILES_DIR,\n ),\n snippetsDir: resolveConfiguredOption(\n this,\n 'snippetsDir',\n options.snippetsDir,\n commandDefaults.snippetsDir,\n process.env.BROWSE_TOOL_SNIPPETS_DIR,\n ),\n };\n try {\n const requestedPort = Number.parseInt(resolvedOptions.port, 10);\n const serviceName = 'browse-tool-http';\n const environment = process.env.NODE_ENV || 'development';\n const repositoryPath = resolveWorkspaceRoot(process.cwd());\n\n // Set environment variables from CLI options\n const registryPath =\n resolvedOptions.registryPath || resolvedOptions.registryDir || process.env.PLAYWRIGHT_REGISTRY_PATH;\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n process.env.PLAYWRIGHT_HOST = resolvedOptions.host;\n process.env.PLAYWRIGHT_PORT = resolvedOptions.port;\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR] = resolvedOptions.idleTimeout;\n\n if (resolvedOptions.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = resolvedOptions.pidsDir;\n }\n if (resolvedOptions.profilesDir) {\n process.env.PLAYWRIGHT_PROFILES_DIR = resolvedOptions.profilesDir;\n }\n if (resolvedOptions.snippetsDir) {\n process.env.BROWSE_TOOL_SNIPPETS_DIR = path.resolve(resolvedOptions.snippetsDir);\n }\n\n console.log('Starting HTTP server for browser automation...');\n console.log(` Port: ${requestedPort}`);\n console.log(` Host: ${resolvedOptions.host}`);\n console.log(` Headless: ${resolvedOptions.headless}`);\n console.log(` Idle Timeout: ${resolvedOptions.idleTimeout} minutes`);\n if (process.env.BROWSE_TOOL_SNIPPETS_DIR) {\n console.log(` Snippets Dir: ${process.env.BROWSE_TOOL_SNIPPETS_DIR}`);\n }\n\n // Create InversifyJS container with full HTTP server services\n const container = createHttpContainer();\n\n // Create HTTP server\n const app = createHttpServer(container);\n\n // Setup WebSocket support for extension communication\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });\n setupWebSocketRoutes(app, container, upgradeWebSocket);\n\n // Configure WebSocketHub event handlers for extension communication\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n PLAYWRIGHT_TYPES.PageRegistry,\n );\n\n // Start idle cleanup service\n const idleCleanupService = container.get<IIdleCleanupService>(PLAYWRIGHT_TYPES.IdleCleanupService);\n idleCleanupService.start();\n\n wsHub.setEventHandlers({\n onSessionRegister: (connection, message) => {\n if (message.type === 'session:register') {\n const requestedBrowserId = message.payload.browserId || connection.browserId;\n const existingBrowser = browserService.getBrowser(requestedBrowserId);\n\n let browserId: string;\n let pageId: string;\n\n if (existingBrowser && (existingBrowser.mode === 'extension' || existingBrowser.mode === 'vm')) {\n browserId = existingBrowser.id;\n const existingPages = pageRegistry.findByBrowser(browserId);\n pageId = existingBrowser.currentPageId || existingPages[0]?.id || '';\n\n wsHub.reassignConnection(connection.id, browserId);\n\n if (!pageId) {\n pageId = pageRegistry.registerExtensionPage(browserId);\n existingBrowser.pageIds.add(pageId);\n existingBrowser.currentPageId = pageId;\n }\n\n console.log(`[WebSocket] Extension connected to existing browser: ${browserId}, pageId: ${pageId}`);\n } else {\n browserId = requestedBrowserId;\n browserService.registerExtensionBrowserWithId(browserId);\n pageId = pageRegistry.registerExtensionPage(browserId);\n\n const browserInstance = browserService.getBrowser(browserId);\n if (browserInstance) {\n browserInstance.pageIds.add(pageId);\n browserInstance.currentPageId = pageId;\n }\n\n console.log(`[WebSocket] Extension connected with new browser: ${browserId}, pageId: ${pageId}`);\n }\n\n const pageEntry = pageRegistry.get(pageId);\n if (pageEntry) {\n pageEntry.extensionTabId = message.payload.tabId;\n pageEntry.url = message.payload.url ?? pageEntry.url;\n }\n\n const session = sessionRegistry.register({\n browserId,\n tabId: message.payload.tabId,\n url: message.payload.url,\n metadata: {\n transport: 'websocket',\n },\n });\n wsHub.sendSessionAck(connection, message.id ?? '', session.id, session.controlMode);\n\n wsHub.broadcastPageCreated(browserId, pageId, pageRegistry.get(pageId)?.url);\n }\n },\n onTaskResult: (_connection, message) => {\n if (message.type === 'task:result') {\n const task = taskQueue.submitResult({\n taskId: message.payload.taskId,\n success: message.payload.success,\n result: message.payload.result,\n error: message.payload.error,\n });\n\n if (task?.browserId) {\n browserService.recordBrowserActivity(task.browserId, task.pageId);\n }\n }\n },\n onTabMapped: (_connection, message) => {\n if (message.type === 'tab:mapped') {\n const pageEntry = pageRegistry.get(message.payload.pageId);\n if (!pageEntry) {\n return;\n }\n\n pageEntry.extensionTabId = message.payload.tabId;\n browserService.recordBrowserActivity(pageEntry.browserId, pageEntry.id);\n }\n },\n onDisconnect: (connection) => {\n if (connection.sessionId) {\n sessionRegistry.removeSession(connection.sessionId);\n }\n console.log(`[WebSocket] Extension disconnected: ${connection.browserId}`);\n },\n });\n\n // Register service in global registry with the exact requested port.\n // Port fallback/retry logic is handled by HttpServerManager, not here.\n const portRegistry = new PortRegistryService(process.env.PORT_REGISTRY_PATH);\n\n if (registryPath) {\n console.log(` Registry path: ${registryPath}`);\n } else {\n console.log(' Registry path: default (~/.port-registry/ports.json)');\n }\n\n const result = await portRegistry.reservePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n preferredPort: requestedPort,\n pid: process.pid,\n host: resolvedOptions.host,\n force: true,\n portRange: { min: requestedPort, max: requestedPort },\n metadata: {\n healthCheckUrl: `${buildPlaywrightBaseUrl(resolvedOptions.host, requestedPort)}/health`,\n headless: resolvedOptions.headless,\n idleTimeout: resolvedOptions.idleTimeout,\n },\n });\n\n if (!result.success || !result.record) {\n throw new Error(result.error || `Failed to reserve port ${requestedPort} in global registry`);\n }\n\n const finalPort = requestedPort;\n\n // Start server with WebSocket support\n let server;\n try {\n server = serve({\n fetch: app.fetch,\n port: finalPort,\n hostname: resolvedOptions.host,\n });\n } catch (error) {\n await portRegistry.releasePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n pid: process.pid,\n });\n throw error;\n }\n\n // Inject WebSocket handler into the server\n injectWebSocket(server);\n\n const baseUrl = buildPlaywrightBaseUrl(resolvedOptions.host, finalPort);\n console.log(`HTTP server listening on ${baseUrl}`);\n console.log(` Health check: ${baseUrl}/health`);\n console.log(` Execute tool: POST ${baseUrl}/execute`);\n\n console.log('\\nPress Ctrl+C to stop the server');\n\n // Graceful shutdown handling\n const shutdown = async (signal: string) => {\n console.log(`\\n\\n${signal} received. Shutting down gracefully...`);\n\n try {\n // Stop idle cleanup\n idleCleanupService.stop();\n\n // Close server\n server.close();\n console.log('Server closed');\n\n // Close all browsers\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n await browserService.closeAll();\n console.log('All browsers closed');\n } catch {\n // Browser service may not have browsers\n }\n\n // Deregister service\n try {\n await portRegistry.releasePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n pid: process.pid,\n });\n console.log('Service deregistered');\n } catch {\n // Ignore errors during cleanup\n }\n\n console.log('Goodbye!');\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n // Register signal handlers\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n } catch (error) {\n console.error('Error starting HTTP server:', error);\n process.exit(1);\n }\n });\n","/**\n * Tool tag definitions for filtering MCP tools by category.\n *\n * DESIGN PATTERNS:\n * - Centralized tag registry pattern\n * - Maps tool names to tag arrays for proxy-level filtering\n * - Tags are additive — a tool can belong to multiple categories\n *\n * CODING STANDARDS:\n * - Keep tag names lowercase, single-word where possible\n * - Keep map in sync with tool registrations in container\n * - Add new tools here when creating them\n *\n * AVOID:\n * - Defining tags in individual tool files (single source of truth)\n * - Using tags not listed in AVAILABLE_TAGS\n */\n\n/** All recognized tag values */\nexport const AVAILABLE_TAGS = [\n 'input',\n 'navigation',\n 'snapshot',\n 'page',\n 'dialog',\n 'network',\n 'console',\n 'script',\n 'emulation',\n 'testing',\n 'tracing',\n 'profile',\n 'browser',\n 'spec',\n 'code',\n] as const;\n\nexport type ToolTag = (typeof AVAILABLE_TAGS)[number];\n\n/** Maps each tool name to its tags */\nexport const TOOL_TAGS: Record<string, ToolTag[]> = {\n // Input tools\n browser_click: ['input'],\n browser_fill: ['input'],\n browser_type: ['input'],\n browser_select: ['input'],\n browser_hover: ['input'],\n browser_drag: ['input'],\n browser_press_key: ['input'],\n browser_upload_file: ['input'],\n // Navigation tools\n browser_navigate: ['navigation'],\n browser_go_back: ['navigation'],\n browser_go_forward: ['navigation'],\n browser_reload: ['navigation'],\n browser_wait_for: ['navigation'],\n // Snapshot tools\n browser_snapshot: ['snapshot'],\n browser_screenshot: ['snapshot'],\n browser_pdf: ['snapshot'],\n // Page management tools\n browser_list_pages: ['page'],\n browser_new_page: ['page'],\n browser_select_page: ['page'],\n browser_close_page: ['page'],\n browser_resize_page: ['page', 'emulation'],\n // Dialog tools\n browser_handle_dialog: ['dialog'],\n // Network tools\n browser_list_network_requests: ['network'],\n browser_get_network_request: ['network'],\n // Console tools\n browser_list_console_messages: ['console'],\n // Script tools\n browser_evaluate_script: ['script'],\n // Emulation tools\n browser_emulate: ['emulation'],\n // Testing and tracing tools\n browser_expect: ['testing'],\n browser_start_trace: ['tracing'],\n browser_stop_trace: ['tracing'],\n // Profile management tools\n browser_list_profiles: ['profile'],\n browser_delete_profile: ['profile'],\n // Spec tools\n run_spec: ['spec', 'testing'],\n discover_specs: ['spec', 'testing'],\n // Browser lifecycle tools\n browser_launch: ['browser'],\n browser_close: ['browser'],\n // Code execution tools\n browser_run_code: ['code', 'script'],\n};\n\n/**\n * Resolves a set of tag names to matching tool names.\n * @param tags - Tags to include\n * @returns Set of tool names that match any of the given tags\n */\nexport function getToolNamesByTags(tags: string[]): Set<string> {\n const result = new Set<string>();\n for (const [toolName, toolTags] of Object.entries(TOOL_TAGS)) {\n if (toolTags.some((t) => tags.includes(t))) {\n result.add(toolName);\n }\n }\n return result;\n}\n","/**\n * Proxy MCP Server\n *\n * DESIGN PATTERNS:\n * - Proxy pattern for forwarding requests to HTTP server\n * - MCP Server setup pattern with request handlers\n * - Session tracking for cleanup on shutdown\n *\n * CODING STANDARDS:\n * - Create server instance using Server from @modelcontextprotocol/sdk\n * - Use proper error handling in request handlers\n * - Export factory function for server creation\n * - Track browsers/pages for session cleanup\n *\n * AVOID:\n * - Business logic in handlers\n * - Hardcoded URLs or configuration\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { getToolNamesByTags } from '../constants/tool-tags.js';\nimport type { IMcpSessionTracker } from '../services/McpSessionTracker.js';\nimport type { ToolDefinition } from '../types/index.js';\n\n/** Browser launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/**\n * Tool filtering configuration for proxy server.\n * When tags are specified, only tools matching those tags are included.\n * Excluded tools are then removed from the result.\n */\nexport interface ToolFilterConfig {\n /** Include only tools matching these tags */\n tags?: string[];\n /** Exclude specific tools by name (applied after tag filtering) */\n exclude?: string[];\n}\n\n/**\n * Configuration for the proxy server\n */\nexport interface ProxyServerConfig {\n /** Base URL of the HTTP server to proxy requests to */\n httpBaseUrl: string;\n /** Session tracker for tracking browsers/pages created in this session */\n sessionTracker?: IMcpSessionTracker;\n /** Default launch mode for browser_launch tool when not specified */\n defaultMode?: LaunchMode;\n /** Tool filtering options */\n toolFilter?: ToolFilterConfig;\n /** Optional directory containing custom tool scripts */\n customToolsDir?: string;\n /** Pre-selected profile enforced for this MCP session */\n enforcedProfileName?: string;\n}\n\n/**\n * Response from the /tools endpoint\n */\ninterface ToolsResponse {\n tools: ToolDefinition[];\n error?: string;\n}\n\ninterface CustomToolDescriptor {\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: ToolDefinition['inputSchema'];\n capabilities?: Record<string, unknown>;\n}\n\ninterface CustomToolsResponse {\n tools: CustomToolDescriptor[];\n error?: string;\n}\n\n/**\n * Response from the /execute endpoint\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: {\n content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n isError?: boolean;\n };\n error?: string;\n}\n\ninterface ProfileListResponse {\n profileCount?: number;\n profiles?: Array<{\n name?: string;\n browserType?: string;\n viewport?: unknown;\n userAgent?: string | null;\n locale?: string | null;\n timezone?: string | null;\n colorScheme?: string | null;\n createdAt?: string;\n updatedAt?: string;\n }>;\n}\n\nconst TOOLS_REQUEST_TIMEOUT_MS = 3000;\nconst TOOLS_REQUEST_MAX_ATTEMPTS = 4;\nconst TOOLS_REQUEST_RETRY_BASE_DELAY_MS = 150;\n\n/**\n * Creates a proxy MCP server that forwards all requests to an HTTP server.\n *\n * This server acts as a thin proxy layer:\n * - ListTools: Fetches tool definitions from HTTP server's /tools endpoint\n * - CallTool: Forwards tool execution to HTTP server's /execute endpoint\n *\n * @param config - Proxy server configuration\n * @returns Configured MCP Server instance\n */\n/** Tools that create browsers */\nconst BROWSER_CREATING_TOOLS = ['browser_launch'];\n/** Tools that create pages */\nconst PAGE_CREATING_TOOLS = ['browser_new_page'];\n\n/**\n * Parse browser/page info from tool result\n */\nfunction parseResourceFromResult(\n result: ExecuteResponse['result'],\n): { browserId?: string; pageId?: string; mode?: string; url?: string } | null {\n if (!result?.content?.[0]?.text) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(result.content[0].text);\n return {\n browserId: parsed.browserId,\n pageId: parsed.pageId,\n mode: parsed.mode,\n url: parsed.url,\n };\n } catch {\n return null;\n }\n}\n\nasync function wait(delayMs: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n}\n\nasync function fetchToolsFromHttpServer(httpBaseUrl: string): Promise<ToolDefinition[]> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TOOLS_REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(`${httpBaseUrl}/tools`, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as ToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n if (!Array.isArray(data.tools)) {\n throw new Error('Invalid /tools response: missing tools array');\n }\n\n if (data.tools.length === 0) {\n throw new Error('HTTP server returned an empty tool list');\n }\n\n return data.tools;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction buildCustomToolsUrl(httpBaseUrl: string, customToolsDir: string): string {\n const url = new URL('/custom-tools', httpBaseUrl);\n url.searchParams.set('dir', customToolsDir);\n return url.toString();\n}\n\nasync function fetchCustomToolsFromHttpServer(\n httpBaseUrl: string,\n customToolsDir: string,\n): Promise<CustomToolDescriptor[]> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TOOLS_REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(buildCustomToolsUrl(httpBaseUrl, customToolsDir), { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as CustomToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n if (!Array.isArray(data.tools)) {\n throw new Error('Invalid /custom-tools response: missing tools array');\n }\n\n return data.tools;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nasync function fetchToolsWithRetry(httpBaseUrl: string): Promise<ToolDefinition[]> {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= TOOLS_REQUEST_MAX_ATTEMPTS; attempt += 1) {\n try {\n return await fetchToolsFromHttpServer(httpBaseUrl);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < TOOLS_REQUEST_MAX_ATTEMPTS) {\n await wait(TOOLS_REQUEST_RETRY_BASE_DELAY_MS * attempt);\n }\n }\n }\n\n throw new Error(\n `Failed to fetch tools after ${TOOLS_REQUEST_MAX_ATTEMPTS} attempts: ${lastError?.message ?? 'Unknown error'}`,\n { cause: lastError },\n );\n}\n\nasync function fetchCustomToolsWithRetry(httpBaseUrl: string, customToolsDir: string): Promise<CustomToolDescriptor[]> {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= TOOLS_REQUEST_MAX_ATTEMPTS; attempt += 1) {\n try {\n return await fetchCustomToolsFromHttpServer(httpBaseUrl, customToolsDir);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < TOOLS_REQUEST_MAX_ATTEMPTS) {\n await wait(TOOLS_REQUEST_RETRY_BASE_DELAY_MS * attempt);\n }\n }\n }\n\n throw new Error(\n `Failed to fetch custom tools after ${TOOLS_REQUEST_MAX_ATTEMPTS} attempts: ${lastError?.message ?? 'Unknown error'}`,\n { cause: lastError },\n );\n}\n\n/**\n * Build the set of allowed tool names based on filter config.\n * Returns null if no filtering is configured (all tools allowed).\n */\nfunction buildAllowedToolNames(filter?: ToolFilterConfig): Set<string> | null {\n if (!filter?.tags?.length && !filter?.exclude?.length) {\n return null;\n }\n\n const taggedNames = filter.tags?.length ? getToolNamesByTags(filter.tags) : null;\n const excludeSet = new Set(filter.exclude ?? []);\n\n if (!taggedNames) {\n // Only exclude, no tag filter — return null to allow all, exclusion applied separately\n return excludeSet.size > 0 ? excludeSet : null;\n }\n\n // Remove excluded tools from the tag-matched set\n for (const name of excludeSet) {\n taggedNames.delete(name);\n }\n\n return taggedNames;\n}\n\nexport function createProxyServer(config: ProxyServerConfig): Server {\n const { httpBaseUrl, sessionTracker, defaultMode, toolFilter, customToolsDir, enforcedProfileName } = config;\n\n // Pre-compute allowed tool names for filtering\n const allowedToolNames = toolFilter?.tags?.length ? buildAllowedToolNames(toolFilter) : null;\n const excludedToolNames = new Set(toolFilter?.exclude ?? []);\n const hasFilter = allowedToolNames !== null || excludedToolNames.size > 0;\n\n const server = new Server(\n {\n name: 'browse-tool',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Session-specific tool definition\n const sessionToolDefinition: ToolDefinition = {\n name: 'browser_list_session',\n description:\n 'List all browsers and pages created in this MCP session. Use this to see what resources you have available.',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n\n let cachedTools: ToolDefinition[] = [];\n let cachedCustomTools: CustomToolDescriptor[] = [];\n\n /**\n * Apply tool filtering based on --tags and --exclude options.\n * When tags are specified, only tools matching those tags are included.\n * Excluded tools are then removed regardless.\n */\n function filterTools(tools: ToolDefinition[]): ToolDefinition[] {\n if (!hasFilter) {\n return tools;\n }\n\n return tools.filter((tool) => {\n if (excludedToolNames.has(tool.name)) {\n return false;\n }\n if (allowedToolNames !== null) {\n return allowedToolNames.has(tool.name);\n }\n return true;\n });\n }\n\n function filterCustomTools(tools: CustomToolDescriptor[]): CustomToolDescriptor[] {\n return tools.filter((tool) => !excludedToolNames.has(tool.name));\n }\n\n function applyEnforcedProfileToLaunchArgs(args: Record<string, unknown>): Record<string, unknown> {\n if (!enforcedProfileName) {\n return args;\n }\n\n return { ...args, profileName: enforcedProfileName };\n }\n\n function rewriteProfileListResult(\n result: NonNullable<ExecuteResponse['result']>,\n ): NonNullable<ExecuteResponse['result']> {\n if (!enforcedProfileName) {\n return result;\n }\n\n const text = result?.content?.[0]?.text;\n if (!text) {\n return result;\n }\n\n try {\n const parsed = JSON.parse(text) as ProfileListResponse;\n const profiles = Array.isArray(parsed.profiles) ? parsed.profiles : [];\n const filteredProfiles = profiles.filter((profile) => profile?.name === enforcedProfileName);\n\n return {\n ...result,\n content: [\n {\n ...result.content[0],\n text: JSON.stringify(\n {\n ...parsed,\n profileCount: filteredProfiles.length,\n profiles: filteredProfiles,\n },\n null,\n 2,\n ),\n },\n ...result.content.slice(1),\n ],\n };\n } catch {\n return result;\n }\n }\n\n function toMcpToolDefinition(tool: CustomToolDescriptor): ToolDefinition {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n annotations: tool.capabilities,\n };\n }\n\n /**\n * Check if a tool name is allowed by the current filter.\n */\n function isToolAllowed(toolName: string): boolean {\n if (excludedToolNames.has(toolName)) {\n return false;\n }\n if (allowedToolNames !== null) {\n return allowedToolNames.has(toolName);\n }\n return true;\n }\n\n /**\n * ListTools handler - fetches tool definitions from HTTP server + session tool\n */\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n try {\n const toolsFromServer = await fetchToolsWithRetry(httpBaseUrl);\n cachedTools = toolsFromServer;\n const customTools = customToolsDir ? await fetchCustomToolsWithRetry(httpBaseUrl, customToolsDir) : [];\n cachedCustomTools = customTools;\n\n // Filter tools based on --tags and --exclude\n const tools = [\n ...filterTools(toolsFromServer),\n ...filterCustomTools(customTools).map((tool) => toMcpToolDefinition(tool)),\n ];\n\n // Add session-specific tool if tracker is available\n if (sessionTracker) {\n tools.push(sessionToolDefinition);\n }\n\n return { tools };\n } catch (error) {\n if (cachedTools.length > 0 || cachedCustomTools.length > 0) {\n console.error(\n 'Failed to refresh tools from HTTP server. Returning cached tools:',\n error instanceof Error ? error.message : String(error),\n );\n\n const tools = [\n ...filterTools(cachedTools),\n ...filterCustomTools(cachedCustomTools).map((tool) => toMcpToolDefinition(tool)),\n ];\n if (sessionTracker) {\n tools.push(sessionToolDefinition);\n }\n return { tools };\n }\n\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to list tools from HTTP server at ${httpBaseUrl}: ${message}`, { cause: error });\n }\n });\n\n /**\n * CallTool handler - forwards tool execution to HTTP server\n */\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n let customToolNames = new Set(cachedCustomTools.map((tool) => tool.name));\n\n // Reject filtered-out tools\n if (name !== 'browser_list_session' && !isToolAllowed(name)) {\n return {\n content: [{ type: 'text', text: `Tool \"${name}\" is not available (filtered by --tags or --exclude)` }],\n isError: true,\n };\n }\n\n // Handle session-specific tool locally\n if (name === 'browser_list_session' && sessionTracker) {\n const state = sessionTracker.getSessionState();\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(state, null, 2),\n },\n ],\n };\n }\n\n // Prepare arguments, injecting defaults where needed\n let finalArgs = (args || {}) as Record<string, unknown>;\n\n // Enforce default mode for browser_launch when configured\n // When default mode is set, always use it (don't allow override)\n if (name === 'browser_launch' && defaultMode) {\n finalArgs = { ...finalArgs, mode: defaultMode };\n }\n if (name === 'browser_launch') {\n finalArgs = applyEnforcedProfileToLaunchArgs(finalArgs);\n }\n\n try {\n if (customToolsDir && !customToolNames.has(name)) {\n cachedCustomTools = await fetchCustomToolsWithRetry(httpBaseUrl, customToolsDir);\n customToolNames = new Set(cachedCustomTools.map((tool) => tool.name));\n }\n\n const isCustomTool = customToolNames.has(name);\n const response = await fetch(\n isCustomTool && customToolsDir ? buildCustomToolsUrl(httpBaseUrl, customToolsDir) : `${httpBaseUrl}/execute`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ tool: name, arguments: finalArgs }),\n },\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n content: [{ type: 'text', text: `HTTP error ${response.status}: ${errorText}` }],\n isError: true,\n };\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n const errorText = data.error || data.result?.content?.[0]?.text || 'Unknown error from HTTP server';\n return {\n content: [{ type: 'text', text: errorText }],\n isError: true,\n };\n }\n\n // Track browsers/pages created in this session\n if (sessionTracker && data.result) {\n if (BROWSER_CREATING_TOOLS.includes(name)) {\n const resource = parseResourceFromResult(data.result);\n if (resource?.browserId) {\n sessionTracker.trackBrowser(\n resource.browserId,\n (resource.mode as 'playwright' | 'extension' | 'vm' | 'stealth') || 'playwright',\n );\n if (resource.pageId) {\n sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n }\n }\n } else if (PAGE_CREATING_TOOLS.includes(name)) {\n const resource = parseResourceFromResult(data.result);\n if (resource?.pageId && resource?.browserId) {\n sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n }\n }\n }\n\n if (name === 'browser_list_profiles' && data.result) {\n return rewriteProfileListResult(data.result);\n }\n\n return data.result || { content: [{ type: 'text', text: 'No result returned' }] };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Failed to execute tool via HTTP server: ${errorMessage}` }],\n isError: true,\n };\n }\n });\n\n return server;\n}\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { PLAYWRIGHT_TYPES, createMcpContainer } from '../container/index.js';\nimport type { ToolFilterConfig } from '../server/proxy.js';\nimport { createProxyServer } from '../server/proxy.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR } from '../services/IdleCleanupService.js';\nimport { McpSessionTracker } from '../services/McpSessionTracker.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\nimport { StreamableHttpTransportHandler } from '../transports/streamable-http.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost, getPlaywrightPort } from '../utils/networkConfig.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst TRANSPORT_STDIO = 'stdio';\nconst TRANSPORT_HTTP = 'http';\nconst TRANSPORT_STREAMABLE_HTTP = 'streamable-http';\nconst SUPPORTED_TRANSPORTS = new Set([TRANSPORT_STDIO, TRANSPORT_HTTP, TRANSPORT_STREAMABLE_HTTP]);\n\nconst BROWSER_CHROMIUM = 'chromium';\nconst BROWSER_FIREFOX = 'firefox';\nconst BROWSER_WEBKIT = 'webkit';\nconst SUPPORTED_BROWSERS = new Set([BROWSER_CHROMIUM, BROWSER_FIREFOX, BROWSER_WEBKIT]);\n\nconst MODE_PLAYWRIGHT = 'playwright';\nconst MODE_EXTENSION = 'extension';\nconst MODE_VM = 'vm';\nconst MODE_STEALTH = 'stealth';\nconst SUPPORTED_LAUNCH_MODES = new Set([MODE_PLAYWRIGHT, MODE_EXTENSION, MODE_VM, MODE_STEALTH]);\n\nconst BROWSER_CLOSE_TOOL = 'browser_close';\nconst CSV_SEPARATOR = ',';\n\nconst HTTP_STATUS_SPAWNED = 'spawned';\nconst HTTP_STATUS_REUSED = 'reused';\n\nconst SIGNAL_SIGINT = 'SIGINT';\nconst SIGNAL_SIGTERM = 'SIGTERM';\n\nconst ENV_REGISTRY_DIR = 'PLAYWRIGHT_REGISTRY_DIR';\nconst ENV_PORT_REGISTRY_PATH = 'PORT_REGISTRY_PATH';\nconst ENV_PIDS_DIR = 'PLAYWRIGHT_PIDS_DIR';\nconst ENV_PROFILES_DIR = 'PLAYWRIGHT_PROFILES_DIR';\nconst ENV_HOST = 'PLAYWRIGHT_HOST';\nconst ENV_PORT = 'PLAYWRIGHT_PORT';\nconst ENV_MCP_PORT = 'PLAYWRIGHT_MCP_PORT';\nconst PROFILE_HEADER = 'x-profile';\n\nconst EXIT_CODE_SUCCESS = 0;\nconst EXIT_CODE_FAILURE = 1;\nconst DEFAULT_STREAMABLE_HTTP_PORT = 3201;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Supported browser types */\nexport type BrowserType = 'chromium' | 'firefox' | 'webkit';\n\n/** Supported launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/** CLI options for mcp-serve command */\nexport interface McpServeOptions {\n type: string;\n browser: BrowserType;\n headless: boolean;\n profile?: string;\n mode?: LaunchMode;\n registryPath?: string;\n host?: string;\n port?: string | number;\n registryDir?: string;\n pidsDir?: string;\n profilesDir?: string;\n tags?: string;\n exclude?: string;\n customTools?: string;\n snippetsDir?: string;\n idleTimeout?: string;\n}\n\n/** Interface for transport handlers */\ninterface TransportHandler {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\nexport class InvalidTransportError extends Error {\n readonly code = 'INVALID_TRANSPORT';\n readonly recovery = `Use --type ${[...SUPPORTED_TRANSPORTS].join(', ')}`;\n readonly transportType: string;\n\n constructor(transportType: string, options?: ErrorOptions) {\n super(`Unknown transport type: ${transportType}`, options);\n this.name = 'InvalidTransportError';\n this.transportType = transportType;\n }\n}\n\nexport class InvalidBrowserTypeError extends Error {\n readonly code = 'INVALID_BROWSER_TYPE';\n readonly recovery = `Use --browser ${[...SUPPORTED_BROWSERS].join(', ')}`;\n readonly browserType: string;\n\n constructor(browserType: string, options?: ErrorOptions) {\n super(`Unknown browser type: ${browserType}`, options);\n this.name = 'InvalidBrowserTypeError';\n this.browserType = browserType;\n }\n}\n\nexport class InvalidLaunchModeError extends Error {\n readonly code = 'INVALID_LAUNCH_MODE';\n readonly recovery = `Use --mode ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`;\n readonly launchMode: string;\n\n constructor(launchMode: string, options?: ErrorOptions) {\n super(`Unknown launch mode: ${launchMode}`, options);\n this.name = 'InvalidLaunchModeError';\n this.launchMode = launchMode;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction parseBrowserType(value: string): BrowserType {\n const browserType = value.toLowerCase();\n if (!SUPPORTED_BROWSERS.has(browserType)) {\n throw new InvalidBrowserTypeError(value);\n }\n return browserType as BrowserType;\n}\n\nfunction parseLaunchMode(value: string): LaunchMode {\n const launchMode = value.toLowerCase();\n if (!SUPPORTED_LAUNCH_MODES.has(launchMode)) {\n throw new InvalidLaunchModeError(value);\n }\n return launchMode as LaunchMode;\n}\n\nfunction parsePort(value: string | number): number {\n const port = typeof value === 'number' ? value : Number.parseInt(value, 10);\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new Error(`Invalid port: ${value}`);\n }\n return port;\n}\n\nfunction normalizeTransportType(value: string): string {\n const transportType = value.toLowerCase();\n return transportType === TRANSPORT_HTTP ? TRANSPORT_STREAMABLE_HTTP : transportType;\n}\n\nfunction getHeaderValue(headers: Record<string, string | string[] | undefined>, name: string): string | undefined {\n const rawValue = headers[name];\n const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;\n return value?.trim() || undefined;\n}\n\n/**\n * Parse a comma-separated string into a trimmed, non-empty string array.\n */\nfunction parseCsv(value: string): string[] {\n return value\n .split(CSV_SEPARATOR)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Build a ToolFilterConfig from the raw --tags and --exclude CLI strings.\n * Returns undefined when no filtering is configured.\n */\nfunction buildToolFilter(options: McpServeOptions): ToolFilterConfig | undefined {\n const tags = options.tags ? parseCsv(options.tags) : undefined;\n const exclude = options.exclude ? parseCsv(options.exclude) : undefined;\n\n if (!tags?.length && !exclude?.length) {\n return undefined;\n }\n\n return { tags, exclude };\n}\n\n/**\n * Typed error for browser cleanup failures with structured context.\n */\nexport class BrowserCleanupError extends Error {\n readonly code = 'BROWSER_CLEANUP_FAILED';\n readonly recovery = 'Browsers may still be running; check the HTTP server or kill processes manually';\n readonly failedBrowserIds: string[];\n readonly httpBaseUrl: string;\n\n constructor(failedBrowserIds: string[], httpBaseUrl: string, options?: ErrorOptions) {\n super(`Failed to close ${failedBrowserIds.length} browser(s): ${failedBrowserIds.join(', ')}`, options);\n this.name = 'BrowserCleanupError';\n this.failedBrowserIds = failedBrowserIds;\n this.httpBaseUrl = httpBaseUrl;\n }\n}\n\n/**\n * Typed error for shutdown failures with structured context.\n */\nexport class SessionShutdownError extends Error {\n readonly code = 'SESSION_SHUTDOWN_FAILED';\n readonly recovery = 'Check logs for cleanup and transport errors; processes may need manual cleanup';\n readonly signal: string;\n\n constructor(signal: string, options?: ErrorOptions) {\n super(`Shutdown failed for signal ${signal}`, options);\n this.name = 'SessionShutdownError';\n this.signal = signal;\n }\n}\n\n/**\n * Typed error for HTTP server startup failures.\n */\nexport class HttpServerStartError extends Error {\n readonly code = 'HTTP_SERVER_START_FAILED';\n readonly recovery = 'Check if the HTTP server binary exists and the port is available';\n\n constructor(options?: ErrorOptions) {\n super('Failed to start HTTP server', options);\n this.name = 'HttpServerStartError';\n }\n}\n\n/**\n * Typed error for MCP server bootstrap failures.\n */\nexport class McpServerBootstrapError extends Error {\n readonly code = 'MCP_SERVER_BOOTSTRAP_FAILED';\n readonly recovery = 'Check if all dependencies are installed and try again';\n\n constructor(options?: ErrorOptions) {\n super('Failed to start MCP server', options);\n this.name = 'McpServerBootstrapError';\n }\n}\n\n/**\n * Close browsers tracked in session via HTTP server.\n * Validates HTTP response status before reporting success.\n * Only clears tracker state when all browsers close successfully.\n */\nasync function closeSessionBrowsers(sessionTracker: McpSessionTracker, httpBaseUrl: string): Promise<void> {\n const browserIds = sessionTracker.getBrowserIds();\n const failedIds: string[] = [];\n let firstCause: Error | undefined;\n\n for (const browserId of browserIds) {\n try {\n const response = await fetch(`${httpBaseUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n tool: BROWSER_CLOSE_TOOL,\n arguments: { browserId },\n }),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(`HTTP ${response.status} from ${httpBaseUrl}/execute: ${body}`);\n }\n\n console.error(` Closed browser: ${browserId}`);\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n console.error(` Failed to close browser: ${browserId}`, cause);\n failedIds.push(browserId);\n firstCause ??= cause;\n }\n }\n\n if (failedIds.length > 0) {\n throw new BrowserCleanupError(failedIds, httpBaseUrl, { cause: firstCause });\n }\n\n sessionTracker.clear();\n}\n\n/**\n * Start MCP server with session-aware cleanup.\n * Uses process.once and an isShuttingDown guard for idempotent shutdown.\n * Always attempts handler.stop() via finally to prevent resource leaks.\n */\nasync function startServerWithSessionCleanup(\n handler: TransportHandler,\n sessionTracker: McpSessionTracker,\n httpBaseUrl: string,\n): Promise<void> {\n await handler.start();\n\n let isShuttingDown = false;\n\n const shutdown = async (signal: string) => {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n let cleanupError: Error | undefined;\n\n try {\n const state = sessionTracker.getSessionState();\n if (state.totalBrowsers > 0) {\n console.error(` Cleaning up ${state.totalBrowsers} browser(s) from this session...`);\n await closeSessionBrowsers(sessionTracker, httpBaseUrl);\n }\n } catch (error) {\n cleanupError = error instanceof Error ? error : new Error(String(error));\n console.error('Browser cleanup error:', cleanupError);\n } finally {\n try {\n await handler.stop();\n } catch (stopError) {\n const cause = stopError instanceof Error ? stopError : new Error(String(stopError));\n console.error('Transport stop error:', cause);\n cleanupError ??= cause;\n }\n\n if (cleanupError) {\n const shutdownError = new SessionShutdownError(signal, { cause: cleanupError });\n console.error('Error during shutdown:', shutdownError);\n process.exit(EXIT_CODE_FAILURE);\n }\n\n process.exit(EXIT_CODE_SUCCESS);\n }\n };\n\n process.once(SIGNAL_SIGINT, () => shutdown(SIGNAL_SIGINT));\n process.once(SIGNAL_SIGTERM, () => shutdown(SIGNAL_SIGTERM));\n}\n\n// ============================================================================\n// Command Definition\n// ============================================================================\n\nexport const mcpServeCommand = new Command('mcp-serve')\n .description('Start Playwright MCP server for browser automation')\n .option(\n '-t, --type <type>',\n `Transport type: ${[TRANSPORT_STDIO, TRANSPORT_STREAMABLE_HTTP].join(', ')}`,\n TRANSPORT_STDIO,\n )\n .option('-b, --browser <browser>', `Default browser type: ${[...SUPPORTED_BROWSERS].join(', ')}`, BROWSER_CHROMIUM)\n .option('--headless', 'Run browsers in headless mode by default', false)\n .option('--no-headless', 'Run browsers in headed mode (visible window)')\n .option('--host <host>', 'Default host for HTTP services', getPlaywrightHost())\n .option('--port <port>', 'Port for streamable HTTP transport', String(DEFAULT_STREAMABLE_HTTP_PORT))\n .option('-p, --profile <name>', 'Default profile name for browser sessions')\n .option('-m, --mode <mode>', `Default launch mode: ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`)\n .option('--tags <tags>', 'Comma-separated tags to filter tools (e.g. input,navigation,snapshot)')\n .option('--exclude <tools>', 'Comma-separated tool names to exclude (applied after --tags)')\n .option('--custom-tools <path>', 'Path to a folder containing tools.yaml and custom tool scripts')\n .option('--snippets-dir <path>', 'Path to a folder where browser_run_code snippets are stored')\n .option('--idle-timeout <minutes>', 'Idle timeout in minutes before auto-closing browsers')\n .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n .option('--registry-dir <path>', 'Custom registry directory for service discovery')\n .option('--pids-dir <path>', 'Custom PIDs directory for process tracking')\n .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n .action(async function (this: Command, options: McpServeOptions) {\n const commandDefaults = getCommandConfig<{\n type?: string;\n browser?: BrowserType;\n headless?: boolean;\n profile?: string;\n mode?: LaunchMode;\n host?: string;\n port?: string | number;\n tags?: string;\n exclude?: string;\n customTools?: string;\n snippetsDir?: string;\n idleTimeout?: number;\n registryPath?: string;\n registryDir?: string;\n pidsDir?: string;\n profilesDir?: string;\n }>('mcpServe');\n const resolvedOptions: McpServeOptions = {\n type: resolveConfiguredOption(this, 'type', options.type, commandDefaults.type),\n browser: resolveConfiguredOption(this, 'browser', options.browser, commandDefaults.browser),\n headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n profile: resolveConfiguredOption(this, 'profile', options.profile, commandDefaults.profile),\n mode: resolveConfiguredOption(this, 'mode', options.mode, commandDefaults.mode),\n host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n port: resolveConfiguredOption(this, 'port', options.port, commandDefaults.port, process.env[ENV_MCP_PORT]),\n tags: resolveConfiguredOption(this, 'tags', options.tags, commandDefaults.tags),\n exclude: resolveConfiguredOption(this, 'exclude', options.exclude, commandDefaults.exclude),\n customTools: resolveConfiguredOption(this, 'customTools', options.customTools, commandDefaults.customTools),\n snippetsDir: resolveConfiguredOption(this, 'snippetsDir', options.snippetsDir, commandDefaults.snippetsDir),\n idleTimeout: resolveConfiguredOption(\n this,\n 'idleTimeout',\n options.idleTimeout,\n commandDefaults.idleTimeout !== undefined ? String(commandDefaults.idleTimeout) : undefined,\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR],\n ),\n registryPath: resolveConfiguredOption(\n this,\n 'registryPath',\n options.registryPath,\n commandDefaults.registryPath,\n process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n ),\n registryDir: resolveConfiguredOption(\n this,\n 'registryDir',\n options.registryDir,\n commandDefaults.registryDir,\n process.env.PLAYWRIGHT_REGISTRY_DIR,\n ),\n pidsDir: resolveConfiguredOption(\n this,\n 'pidsDir',\n options.pidsDir,\n commandDefaults.pidsDir,\n process.env.PLAYWRIGHT_PIDS_DIR,\n ),\n profilesDir: resolveConfiguredOption(\n this,\n 'profilesDir',\n options.profilesDir,\n commandDefaults.profilesDir,\n process.env.PLAYWRIGHT_PROFILES_DIR,\n ),\n };\n const transportType = normalizeTransportType(resolvedOptions.type);\n\n try {\n if (!SUPPORTED_TRANSPORTS.has(transportType)) {\n throw new InvalidTransportError(transportType);\n }\n\n const browserType = parseBrowserType(resolvedOptions.browser);\n const defaultMode = resolvedOptions.mode ? parseLaunchMode(resolvedOptions.mode) : undefined;\n const transportPort = parsePort(resolvedOptions.port ?? DEFAULT_STREAMABLE_HTTP_PORT);\n const toolFilter = buildToolFilter(resolvedOptions);\n const customToolsDir = resolvedOptions.customTools ? path.resolve(resolvedOptions.customTools) : undefined;\n const snippetsDir = resolvedOptions.snippetsDir ? path.resolve(resolvedOptions.snippetsDir) : undefined;\n\n console.error('Playwright MCP Server starting...');\n console.error(` Transport: ${transportType}`);\n if (transportType === TRANSPORT_STREAMABLE_HTTP) {\n console.error(` MCP endpoint: http://${resolvedOptions.host ?? getPlaywrightHost()}:${transportPort}/mcp`);\n }\n console.error(` Default browser: ${browserType}`);\n console.error(` Headless: ${resolvedOptions.headless}`);\n if (defaultMode) {\n console.error(` Default mode: ${defaultMode}`);\n }\n if (resolvedOptions.profile) {\n console.error(` Profile: ${resolvedOptions.profile}`);\n }\n if (toolFilter?.tags?.length) {\n console.error(` Tags: ${toolFilter.tags.join(', ')}`);\n }\n if (toolFilter?.exclude?.length) {\n console.error(` Exclude: ${toolFilter.exclude.join(', ')}`);\n }\n if (customToolsDir) {\n console.error(` Custom tools: ${customToolsDir}`);\n }\n if (snippetsDir) {\n console.error(` Snippets dir: ${snippetsDir}`);\n }\n if (resolvedOptions.idleTimeout) {\n console.error(` Idle timeout: ${resolvedOptions.idleTimeout} minutes`);\n }\n\n const registryPath = resolvedOptions.registryPath || resolvedOptions.registryDir;\n if (registryPath) {\n process.env[ENV_REGISTRY_DIR] = registryPath;\n process.env[ENV_PORT_REGISTRY_PATH] = registryPath;\n console.error(` Registry path: ${registryPath}`);\n }\n if (resolvedOptions.pidsDir) {\n process.env[ENV_PIDS_DIR] = resolvedOptions.pidsDir;\n console.error(` PIDs dir: ${resolvedOptions.pidsDir}`);\n }\n if (resolvedOptions.profilesDir) {\n process.env[ENV_PROFILES_DIR] = resolvedOptions.profilesDir;\n console.error(` Profiles dir: ${resolvedOptions.profilesDir}`);\n }\n if (snippetsDir) {\n process.env.BROWSE_TOOL_SNIPPETS_DIR = snippetsDir;\n }\n if (resolvedOptions.idleTimeout) {\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR] = resolvedOptions.idleTimeout;\n }\n if (resolvedOptions.host) {\n process.env[ENV_HOST] = resolvedOptions.host;\n }\n process.env[ENV_PORT] = getPlaywrightPort().toString();\n\n const localContainer = createMcpContainer();\n\n const httpServerManager = localContainer.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n const httpStatus = await httpServerManager.ensureRunning();\n\n if (!httpStatus.running) {\n throw new HttpServerStartError();\n }\n\n const httpBaseUrl = buildPlaywrightBaseUrl(getPlaywrightHost(), httpStatus.port);\n const httpLabel = httpStatus.spawned ? HTTP_STATUS_SPAWNED : HTTP_STATUS_REUSED;\n console.error(` HTTP server: ${httpLabel} on port ${httpStatus.port}`);\n\n if (transportType === TRANSPORT_STDIO) {\n const sessionTracker = new McpSessionTracker();\n const server = createProxyServer({\n httpBaseUrl,\n sessionTracker,\n defaultMode,\n toolFilter,\n customToolsDir,\n enforcedProfileName: resolvedOptions.profile,\n });\n const handler = new StdioTransportHandler(server);\n\n await startServerWithSessionCleanup(handler, sessionTracker, httpBaseUrl);\n return;\n }\n\n const handler = new StreamableHttpTransportHandler(\n ({ headers }) => {\n const enforcedProfileName = getHeaderValue(headers, PROFILE_HEADER) ?? resolvedOptions.profile;\n const sessionTracker = new McpSessionTracker();\n return {\n server: createProxyServer({\n httpBaseUrl,\n sessionTracker,\n defaultMode,\n toolFilter,\n customToolsDir,\n enforcedProfileName,\n }),\n onClose: async () => {\n const state = sessionTracker.getSessionState();\n if (state.totalBrowsers > 0) {\n await closeSessionBrowsers(sessionTracker, httpBaseUrl);\n }\n },\n };\n },\n {\n host: resolvedOptions.host ?? getPlaywrightHost(),\n port: transportPort,\n },\n );\n\n await handler.start();\n\n let isShuttingDown = false;\n const shutdown = async (signal: string) => {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n await handler.stop();\n process.exit(EXIT_CODE_SUCCESS);\n } catch (stopError) {\n console.error('Transport stop error:', stopError);\n process.exit(EXIT_CODE_FAILURE);\n }\n };\n\n process.once(SIGNAL_SIGINT, () => shutdown(SIGNAL_SIGINT));\n process.once(SIGNAL_SIGTERM, () => shutdown(SIGNAL_SIGTERM));\n } catch (error) {\n if (\n error instanceof InvalidTransportError ||\n error instanceof InvalidBrowserTypeError ||\n error instanceof InvalidLaunchModeError ||\n error instanceof HttpServerStartError\n ) {\n console.error(`Error [${error.code}]: ${error.message}`);\n console.error(`Recovery: ${error.recovery}`);\n process.exit(EXIT_CODE_FAILURE);\n }\n\n const cause = error instanceof Error ? error : new Error(String(error));\n const bootstrapError = new McpServerBootstrapError({ cause });\n console.error(`Error [${bootstrapError.code}]: ${bootstrapError.message}`, bootstrapError.cause);\n console.error(`Recovery: ${bootstrapError.recovery}`);\n process.exit(EXIT_CODE_FAILURE);\n }\n });\n","/**\n * Status Command\n *\n * Shows status of HTTP server and other diagnostics.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\n/**\n * Show status of browse-tool services\n */\nexport const statusCommand = new Command('status')\n .description('Show status of HTTP server and diagnostics')\n .action(async () => {\n try {\n const httpServeDefaults = getCommandConfig<{\n host?: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n }>('httpServe');\n const registryPath =\n process.env.PLAYWRIGHT_REGISTRY_PATH ??\n process.env.PORT_REGISTRY_PATH ??\n httpServeDefaults.registryPath ??\n httpServeDefaults.registryDir;\n\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n }\n\n console.log('browse-tool Status\\n');\n console.log('-'.repeat(50));\n\n // Create container to get HttpServerManager\n const container = createContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n // Get HTTP server status\n const httpStatus = await httpServerManager.getStatus();\n\n if (httpStatus.running) {\n const host = process.env.PLAYWRIGHT_HOST ?? httpServeDefaults.host ?? getPlaywrightHost();\n console.log('HTTP Server: Running');\n console.log(` PID: ${httpStatus.pid}`);\n console.log(` Port: ${httpStatus.port}`);\n console.log(` Health: ${buildPlaywrightBaseUrl(host, httpStatus.port)}/health`);\n if (httpStatus.browserCount !== undefined) {\n console.log(` Browsers: ${httpStatus.browserCount}`);\n }\n } else {\n console.log('HTTP Server: Not Running');\n if (httpStatus.error) {\n console.log(` Error: ${httpStatus.error}`);\n }\n }\n\n console.log('');\n console.log('-'.repeat(50));\n process.exit(0);\n } catch (error) {\n console.error('Error getting status:', error);\n process.exit(1);\n }\n });\n","/**\n * Stop Command\n *\n * Stops HTTP server and cleans up registry/PID files.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\n\n/**\n * Stop HTTP server\n */\nexport const stopCommand = new Command('stop')\n .description('Stop HTTP server and clean up registry/PID files')\n .action(async () => {\n try {\n const httpServeDefaults = getCommandConfig<{\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n }>('httpServe');\n const registryPath =\n process.env.PLAYWRIGHT_REGISTRY_PATH ??\n process.env.PORT_REGISTRY_PATH ??\n httpServeDefaults.registryPath ??\n httpServeDefaults.registryDir;\n\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n }\n\n console.log('Stopping browse-tool services...');\n\n // Create container to get HttpServerManager\n const container = createContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n // Stop HTTP server\n const stopped = await httpServerManager.stop();\n\n if (stopped) {\n console.log('HTTP server stopped');\n console.log('Registry and PID files cleaned up');\n } else {\n console.log('No HTTP server was running');\n }\n\n console.log('Done!');\n process.exit(0);\n } catch (error) {\n console.error('Error stopping services:', error);\n process.exit(1);\n }\n });\n","const ROOT_OPTIONS_WITH_VALUES = new Set(['--config']);\n\nexport function getFirstCommandToken(argv: string[]): string | undefined {\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n\n if (ROOT_OPTIONS_WITH_VALUES.has(arg)) {\n index += 1;\n continue;\n }\n\n if ([...ROOT_OPTIONS_WITH_VALUES].some((option) => arg.startsWith(`${option}=`))) {\n continue;\n }\n\n if (arg.startsWith('-')) {\n continue;\n }\n\n return arg;\n }\n\n return undefined;\n}\n\nexport function shouldRegisterDynamicToolCommands(argv: string[], skipDynamicTools = false): boolean {\n if (skipDynamicTools) {\n return false;\n }\n\n const firstCommand = getFirstCommandToken(argv);\n if (!firstCommand) {\n return false;\n }\n\n if (firstCommand === 'tools') {\n return true;\n }\n\n return firstCommand === 'help' && argv.includes('tools');\n}\n","/**\n * Command Builder Utilities for Tool Commands\n *\n * Converts tool definitions with JSON Schema to Commander.js commands.\n * Handles schema-to-CLI option conversion with proper type handling.\n *\n * DESIGN PATTERNS:\n * - Builder pattern for command construction\n * - Schema-driven command generation\n * - Type-safe option parsing\n *\n * CODING STANDARDS:\n * - Use TypeScript for type safety\n * - Handle all JSON Schema types appropriately\n * - Provide clear CLI conventions (kebab-case)\n *\n * AVOID:\n * - Manual command definitions for each tool\n * - Inconsistent naming conventions\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { Command, Option } from 'commander';\nimport { getCommandConfig, getToolConfig, resolveConfiguredOption } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { type FormatterOptions, type OutputFormat, formatError, formatToolResult } from './outputFormatter.js';\n\n/**\n * JSON Schema property definition\n */\ninterface SchemaProperty {\n type?: string;\n description?: string;\n default?: unknown;\n enum?: string[];\n items?: { type?: string; enum?: string[] };\n oneOf?: SchemaProperty[];\n properties?: Record<string, SchemaProperty>;\n additionalProperties?: boolean | SchemaProperty;\n minimum?: number;\n maximum?: number;\n}\n\n/**\n * Global options inherited by all tool commands\n */\ninterface GlobalOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\nexport interface ToolCommandContext {\n command: Command;\n formatterOptions: FormatterOptions;\n port: number;\n}\n\nexport type ToolCommandExecutor = (\n args: Record<string, unknown>,\n context: ToolCommandContext,\n) => Promise<CallToolResult>;\n\nexport interface ToolCommandDefinition {\n definition: ToolDefinition;\n execute?: ToolCommandExecutor;\n}\n\n/**\n * Convert snake_case to kebab-case for CLI command names\n */\nexport function toKebabCase(str: string): string {\n return str.replace(/_/g, '-');\n}\n\n/**\n * Convert camelCase to kebab-case for CLI option names\n */\nexport function camelToKebab(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\nfunction getSchemaTypes(schema: SchemaProperty): Set<string> {\n const types = new Set<string>();\n\n if (schema.type) {\n types.add(schema.type);\n }\n\n for (const variant of schema.oneOf ?? []) {\n if (variant.type) {\n types.add(variant.type);\n }\n }\n\n return types;\n}\n\n/**\n * Parse a CLI option value based on its JSON Schema type\n */\nexport function parseOptionValue(value: string, schema: SchemaProperty): unknown {\n const types = getSchemaTypes(schema);\n\n if (schema.type === 'number' || schema.type === 'integer') {\n const num = Number(value);\n if (Number.isNaN(num)) {\n throw new Error(`Invalid number: ${value}`);\n }\n return num;\n }\n\n if (schema.type === 'boolean') {\n if (value === 'true' || value === '1') return true;\n if (value === 'false' || value === '0') return false;\n throw new Error(`Invalid boolean: ${value}`);\n }\n\n if (types.has('array') || types.has('object')) {\n try {\n return JSON.parse(value);\n } catch {\n if (schema.type === 'array' || schema.type === 'object') {\n throw new Error(`Invalid JSON: ${value}`);\n }\n }\n }\n\n if (types.has('number') || types.has('integer')) {\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== '') {\n return num;\n }\n }\n\n return value;\n}\n\n/**\n * Build Commander option flag string from property name and schema\n */\nexport function buildOptionFlag(name: string, schema: SchemaProperty): string {\n const kebabName = camelToKebab(name);\n\n if (schema.type === 'boolean') {\n return `--${kebabName}`;\n }\n\n return `--${kebabName} <value>`;\n}\n\n/**\n * Build a Commander command from a tool definition\n */\nexport function buildToolCommand(commandDefinition: ToolCommandDefinition): Command {\n const { definition: tool, execute } = commandDefinition;\n const commandName = toKebabCase(tool.name);\n const command = new Command(commandName);\n\n command.description(tool.description);\n\n const schema = tool.inputSchema;\n const properties = schema.properties || {};\n const required = new Set(schema.required || []);\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const prop = propSchema as SchemaProperty;\n const flag = buildOptionFlag(propName, prop);\n let description = prop.description || '';\n\n if (prop.enum && prop.enum.length > 0) {\n description += ` (choices: ${prop.enum.join(', ')})`;\n }\n\n if (prop.default !== undefined) {\n description += ` (default: ${JSON.stringify(prop.default)})`;\n }\n\n if (required.has(propName)) {\n description += ' [required]';\n }\n\n if (prop.type === 'boolean') {\n if (prop.default === true) {\n command.option(`--no-${camelToKebab(propName)}`, `Disable ${description}`);\n } else {\n command.option(flag, description, prop.default as boolean);\n }\n } else if (prop.enum && prop.enum.length > 0) {\n command.addOption(new Option(flag, description).choices(prop.enum));\n } else {\n command.option(flag, description);\n }\n }\n\n command.action(async function (this: Command) {\n const options = this.optsWithGlobals() as Record<string, unknown> & GlobalOptions;\n const commandDefaults = getCommandConfig<Partial<GlobalOptions>>('tools');\n const format = resolveConfiguredOption(\n this,\n 'format',\n (options.format ?? 'json') as OutputFormat,\n commandDefaults.format,\n );\n const color = resolveConfiguredOption(this, 'color', options.color ?? true, commandDefaults.color);\n const portValue = resolveConfiguredOption(\n this,\n 'port',\n String(options.port ?? DEFAULT_MCP_PORT),\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n const formatterOptions: FormatterOptions = { format, color };\n const toolDefaults = getToolConfig(tool.name);\n\n try {\n const args: Record<string, unknown> = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const prop = propSchema as SchemaProperty;\n const kebabName = camelToKebab(propName);\n const optionKey = kebabName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());\n const optionSource = this.getOptionValueSourceWithGlobals(optionKey);\n let value = options[optionKey];\n\n if (prop.type === 'boolean' && prop.default === true) {\n value = options[optionKey];\n }\n\n if (\n (optionSource === undefined || optionSource === 'default' || optionSource === 'implied') &&\n toolDefaults[propName] !== undefined\n ) {\n value = toolDefaults[propName];\n }\n\n if (value !== undefined) {\n if (typeof value === 'string' && prop.type !== 'string') {\n args[propName] = parseOptionValue(value, prop);\n } else {\n args[propName] = value;\n }\n }\n }\n\n for (const reqField of required) {\n if (args[reqField] === undefined) {\n const kebabField = camelToKebab(reqField);\n console.error(formatError(`Missing required option: --${kebabField}`, formatterOptions.color));\n process.exit(1);\n }\n }\n\n const port = Number.parseInt(portValue, 10);\n const client = createToolClient({ port });\n const result = execute\n ? await execute(args, { command: this, formatterOptions, port })\n : await client.execute(tool.name, args);\n const output = formatToolResult(result, formatterOptions);\n\n if (output) {\n console.log(output);\n }\n\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n\n return command;\n}\n\n/**\n * Build commands for all tools from definitions\n */\nexport function buildAllToolCommands(tools: ToolCommandDefinition[]): Command[] {\n return tools.map(buildToolCommand);\n}\n","/**\n * Tool Commands Registration Utilities\n *\n * Dynamically creates CLI commands from tool definitions fetched from the HTTP server.\n * Adds CLI-only shims for MCP capabilities that are not directly exposed as HTTP tools.\n *\n * DESIGN PATTERNS:\n * - Factory pattern for command creation\n * - Pure functions for data transformation\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - Hardcoded tool definitions\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { type ToolCommandDefinition, buildAllToolCommands, toKebabCase } from './commandBuilder.js';\nimport type { BrowserInfo } from './httpClient.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { formatError } from './outputFormatter.js';\n\nconst SESSION_TOOL_NAME = 'browser_list_session';\nconst SESSION_TOOL_DESCRIPTION = 'List browsers and pages currently tracked by the browse-tool HTTP server.';\n\n/**\n * Tool category groupings for help organization\n */\nconst TOOL_CATEGORIES: Record<string, string[]> = {\n browser: [\n 'browser_launch',\n 'browser_close',\n 'browser_start_recording',\n 'browser_stop_recording',\n 'browser_list_pages',\n 'browser_resize_page',\n SESSION_TOOL_NAME,\n ],\n navigation: ['browser_navigate', 'browser_go_back', 'browser_go_forward', 'browser_reload', 'browser_wait_for'],\n interaction: [\n 'browser_click',\n 'browser_type',\n 'browser_fill',\n 'browser_select',\n 'browser_hover',\n 'browser_press_key',\n 'browser_drag',\n 'browser_upload_file',\n ],\n extraction: ['browser_snapshot', 'browser_screenshot', 'browser_pdf'],\n script: ['browser_evaluate_script', 'browser_run_code', 'browser_list_snippets'],\n network: ['browser_list_network_requests', 'browser_get_network_request'],\n dialog: ['browser_handle_dialog'],\n tracing: ['browser_start_trace', 'browser_stop_trace'],\n testing: ['browser_expect', 'run_spec', 'discover_specs'],\n profile: ['browser_list_profiles', 'browser_delete_profile'],\n};\n\nconst SESSION_TOOL_DEFINITION: ToolDefinition = {\n name: SESSION_TOOL_NAME,\n description: SESSION_TOOL_DESCRIPTION,\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n};\n\n/**\n * Get category for a tool name\n */\nexport function getToolCategory(toolName: string): string {\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n if (tools.includes(toolName)) {\n return category;\n }\n }\n return 'other';\n}\n\n/**\n * Create a parent command for tool commands with global options\n */\nexport function createToolsCommand(): Command {\n return new Command('tools')\n .description('Execute browser automation tools')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT));\n}\n\nfunction formatSessionResult(browsers: BrowserInfo[]) {\n return {\n browserCount: browsers.length,\n browsers: browsers.map((browser) => ({\n browserId: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n pageIds: browser.pageIds,\n createdAt: browser.createdAt,\n })),\n };\n}\n\nfunction createSessionToolCommandDefinition(): ToolCommandDefinition {\n return {\n definition: SESSION_TOOL_DEFINITION,\n execute: async (_args, context) => {\n const client = createToolClient({ port: context.port });\n const browsers = await client.listBrowsers();\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(formatSessionResult(browsers), null, 2),\n },\n ],\n };\n },\n };\n}\n\nfunction buildCommandDefinitions(tools: ToolDefinition[]): ToolCommandDefinition[] {\n return [...tools.map((definition) => ({ definition })), createSessionToolCommandDefinition()];\n}\n\n/**\n * Register tool commands under a parent command\n * Fetches tool definitions from the HTTP server and creates corresponding CLI commands\n */\nexport async function registerToolCommands(parentCommand: Command, options?: { port?: number }): Promise<void> {\n const commandDefaults = getCommandConfig<{ port?: number }>('tools');\n const port = Number(options?.port ?? process.env.PLAYWRIGHT_PORT ?? commandDefaults.port ?? DEFAULT_MCP_PORT);\n\n try {\n const client = createToolClient({ port });\n const tools = await client.listTools();\n const toolCommands = buildAllToolCommands(buildCommandDefinitions(tools));\n\n for (const cmd of toolCommands) {\n parentCommand.addCommand(cmd);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`${formatError(`Failed to load tool commands: ${errorMessage}`, true)}\\n`);\n process.stderr.write('Make sure the HTTP server is running: browse-tool http-serve\\n');\n }\n}\n\n/**\n * Create tool commands from static definitions (for offline use)\n */\nexport function createToolCommandsFromDefinitions(tools: ToolDefinition[]): Command[] {\n return buildAllToolCommands(buildCommandDefinitions(tools));\n}\n\n/**\n * Get help text for tool categories\n */\nexport function getCategoryHelp(): string {\n const lines: string[] = ['Tool Categories:'];\n\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n const kebabTools = tools.map(toKebabCase).join(', ');\n lines.push(` ${category}: ${kebabTools}`);\n }\n\n return lines.join('\\n');\n}\n","#!/usr/bin/env node\nimport 'reflect-metadata/lite';\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { chromeServeCommand } from './commands/chrome-serve.js';\nimport { execCustomToolCommand, listCustomToolsCommand } from './commands/custom-tools.js';\nimport { dockerBuildCftCommand } from './commands/docker-build-cft.js';\nimport { execCommand } from './commands/exec.js';\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { statusCommand } from './commands/status.js';\nimport { stopCommand } from './commands/stop.js';\nimport { initializeCliRuntime } from './config.js';\nimport { shouldRegisterDynamicToolCommands } from './utils/cliArgs.js';\nimport { DEFAULT_MCP_PORT } from './utils/networkConfig.js';\nimport { createToolsCommand, registerToolCommands } from './utils/toolCommands.js';\n\nconst SKIP_DYNAMIC_TOOLS_ENV = 'PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS';\n\nfunction resolveDynamicToolsPort(argv: string[], fallbackPort: string | number): number {\n const args = [...argv];\n\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index];\n\n if (arg === '--port' || arg === '-p') {\n const candidate = args[index + 1];\n const parsed = Number(candidate);\n if (!Number.isNaN(parsed) && candidate) {\n return parsed;\n }\n continue;\n }\n\n if (arg.startsWith('--port=')) {\n const parsed = Number(arg.slice('--port='.length));\n if (!Number.isNaN(parsed)) {\n return parsed;\n }\n continue;\n }\n\n if (arg.startsWith('-p=')) {\n const parsed = Number(arg.slice('-p='.length));\n if (!Number.isNaN(parsed)) {\n return parsed;\n }\n }\n }\n\n return Number(fallbackPort);\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n const runtime = initializeCliRuntime(process.argv.slice(2));\n const configuredToolsPort =\n process.env.PLAYWRIGHT_PORT ??\n runtime.config.commands.tools?.port ??\n runtime.config.commands.exec?.port ??\n DEFAULT_MCP_PORT;\n const program = new Command();\n\n program\n .name('browse-tool')\n .description(\n 'MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support',\n )\n .option('--config <path>', 'Path to browse-tool config file or config directory')\n .version(packageJson.version);\n\n // Add all commands\n program.addCommand(mcpServeCommand);\n program.addCommand(httpServeCommand);\n program.addCommand(chromeServeCommand);\n program.addCommand(dockerBuildCftCommand);\n program.addCommand(stopCommand);\n program.addCommand(statusCommand);\n program.addCommand(execCommand);\n program.addCommand(listCustomToolsCommand);\n program.addCommand(execCustomToolCommand);\n\n // Create and register tool commands (dynamically generated from HTTP server)\n const toolsCommand = createToolsCommand();\n if (shouldRegisterDynamicToolCommands(process.argv.slice(2), process.env[SKIP_DYNAMIC_TOOLS_ENV] === '1')) {\n await registerToolCommands(toolsCommand, {\n port: resolveDynamicToolsPort(process.argv.slice(2), configuredToolsPort),\n });\n }\n program.addCommand(toolsCommand);\n\n // Parse arguments\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":["Hono","telemetry: ITelemetryService","container","PLAYWRIGHT_TYPES","TelemetryService","resolveBrowserTelemetryConfig","result: ExtensionTaskResult","response: StatusResponse","ContainerModule","PLAYWRIGHT_TYPES","ExtensionTaskQueue","ExtensionToolDelegator","Server","toolDefinitions: ToolDefinition[]","ListToolsRequestSchema","CallToolRequestSchema","Command","container","Container","Hono","StdioServerTransport","DEFAULT_CONFIG: BrowseToolConfig","z","runtimeContext: CliRuntimeContext","path","createMcpContainer","PLAYWRIGHT_TYPES","lines: string[]","Command","DEFAULT_MCP_PORT","args: Record<string, unknown>","Command","buildDockerChromeForTestingImage","resolveChromeForTestingArchivePlatform","Command","DEFAULT_MCP_PORT","formatterOptions: FormatterOptions","args: Record<string, unknown>","z","attributes: Attributes","result: unknown[]","objectValue: Record<string, unknown>","result: Record<string, unknown>","pageRegistry: IPageRegistry","extensionTaskQueue?: ExtensionTaskQueue","telemetry: ITelemetryService","TelemetryService","browserService?: IBrowserService","ExtensionPageProxy","pageId","delegatedPayload: { url?: string; title?: string; tabId?: number }","pageEntry","browser","baseAttributes: Record<string, unknown>","path","SpanStatusCode","Hono","container","PLAYWRIGHT_TYPES","browsers: BrowserResponse[]","stats: StatsResponse","styles.tableContainer","styles.emptyState","styles.table","styles.browserRow","styles.statusActive","styles.timestampCell","styles.actionsCell","styles.killButton","styles.pageRow","styles.urlCell","styles.statsContainer","styles.statCard","styles.container","styles.header","styles.button","styles.refreshButton","styles.buttonDanger","styles.statCard","styles.tableContainer","styles.emptyState","styles.browserRow","styles.statusActive","styles.timestampCell","styles.actionsCell","styles.killButton","styles.pageRow","styles.urlCell","Hono","container","PLAYWRIGHT_TYPES","browsers: BrowserData[]","container","PLAYWRIGHT_TYPES","TelemetryService","Hono","response: HealthResponse","SpanStatusCode","browserService","connectionId: string | null","container","PLAYWRIGHT_TYPES","path","Command","DEFAULT_MCP_PORT","getPlaywrightHost","resolvedOptions: HttpServeOptions","BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR","container","createHttpContainer","PLAYWRIGHT_TYPES","browserId: string","pageId: string","PortRegistryService","buildPlaywrightBaseUrl","TOOL_TAGS: Record<string, ToolTag[]>","lastError: Error | undefined","Server","sessionToolDefinition: ToolDefinition","cachedTools: ToolDefinition[]","cachedCustomTools: CustomToolDescriptor[]","cachedBuiltinToolNames: Set<string>","ListToolsRequestSchema","CallToolRequestSchema","failedIds: string[]","firstCause: Error | undefined","cleanupError: Error | undefined","Command","getPlaywrightHost","resolvedOptions: McpServeOptions","BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR","path","getPlaywrightPort","createMcpContainer","PLAYWRIGHT_TYPES","buildPlaywrightBaseUrl","McpSessionTracker","StdioTransportHandler","StreamableHttpTransportHandler","Command","createContainer","PLAYWRIGHT_TYPES","getPlaywrightHost","buildPlaywrightBaseUrl","Command","createContainer","PLAYWRIGHT_TYPES","Command","Option","DEFAULT_MCP_PORT","formatterOptions: FormatterOptions","args: Record<string, unknown>","SESSION_TOOL_DEFINITION: ToolDefinition","Command","DEFAULT_MCP_PORT","DEFAULT_MCP_PORT","Command","packageJson.version"],"sources":["../package.json","../src/server/extension-routes.ts","../src/commands/chrome-serve.ts","../src/config.ts","../src/utils/httpClient.ts","../src/utils/outputFormatter.ts","../src/commands/custom-tools.ts","../src/commands/docker-build-cft.ts","../src/commands/exec.ts","../src/services/CustomToolService.ts","../src/server/dashboard/routes/api.ts","../src/server/dashboard/utils/formatters.ts","../src/server/dashboard/components/styles.ts","../src/server/dashboard/components/BrowserTable.tsx","../src/server/dashboard/components/StatsHeader.tsx","../src/server/dashboard/components/Dashboard.tsx","../src/server/dashboard/components/Layout.tsx","../src/server/dashboard/routes/dashboard.tsx","../src/server/http.ts","../src/server/websocket-routes.ts","../src/commands/http-serve.ts","../src/constants/tool-tags.ts","../src/server/proxy.ts","../src/commands/mcp-serve.ts","../src/commands/status.ts","../src/commands/stop.ts","../src/utils/cliArgs.ts","../src/utils/commandBuilder.ts","../src/utils/toolCommands.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@agimon-ai/browse-tool\",\n \"description\": \"MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support\",\n \"version\": \"0.2.8\",\n \"license\": \"BUSL-1.1\",\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"playwright\",\n \"browser-automation\",\n \"typescript\"\n ],\n \"bin\": {\n \"browse-tool\": \"./dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"types\": \"./dist/index.d.cts\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"dev\": \"tsx src/cli.ts mcp-serve\",\n \"build\": \"tsdown && pnpm build:extension\",\n \"build:extension\": \"vite build --config vite.config.extension.ts\",\n \"build:vm-image\": \"node ./scripts/build-vm-image.mjs\",\n \"dev:extension\": \"vite build --config vite.config.extension.ts --watch --mode development\",\n \"test\": \"vitest --run\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src\"\n },\n \"dependencies\": {\n \"@agimon-ai/foundation-port-registry\": \"workspace:*\",\n \"@hono/node-server\": \"1.19.9\",\n \"@hono/node-ws\": \"^1.3.0\",\n \"@modelcontextprotocol/sdk\": \"1.19.1\",\n \"@opentelemetry/api\": \"1.9.0\",\n \"@opentelemetry/api-logs\": \"0.213.0\",\n \"@opentelemetry/exporter-logs-otlp-http\": \"0.213.0\",\n \"@opentelemetry/exporter-trace-otlp-http\": \"0.213.0\",\n \"@opentelemetry/resources\": \"2.6.0\",\n \"@opentelemetry/sdk-logs\": \"0.213.0\",\n \"@opentelemetry/sdk-trace-node\": \"2.6.0\",\n \"@opentelemetry/semantic-conventions\": \"1.40.0\",\n \"@playwright/test\": \"1.57.0\",\n \"chalk\": \"5.6.2\",\n \"commander\": \"14.0.1\",\n \"hono\": \"4.11.4\",\n \"inversify\": \"7.0.1\",\n \"liquidjs\": \"^10.24.0\",\n \"playwright\": \"1.57.0\",\n \"reflect-metadata\": \"0.2.2\",\n \"esbuild\": \"^0.25.0\",\n \"ws\": \"8.18.0\",\n \"zod\": \"^4.3.6\"\n },\n \"devDependencies\": {\n \"@types/chrome\": \"0.0.322\",\n \"@types/node\": \"^22.0.0\",\n \"@types/ws\": \"8.5.13\",\n \"tsdown\": \"0.16.1\",\n \"typescript\": \"5.9.3\",\n \"unplugin-raw\": \"^0.6.4\",\n \"vite\": \"7.0.5\",\n \"vite-plugin-static-copy\": \"^3.1.5\",\n \"vitest\": \"4.0.18\"\n },\n \"type\": \"module\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./cli\": {\n \"import\": \"./dist/cli.mjs\",\n \"require\": \"./dist/cli.cjs\"\n },\n \"./stubs/playwright-test\": {\n \"import\": \"./dist/stubs/playwright-test.mjs\",\n \"require\": \"./dist/stubs/playwright-test.cjs\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","/**\n * Extension HTTP Routes\n *\n * DESIGN PATTERNS:\n * - Hono router for HTTP routing\n * - RESTful API endpoints for extension communication\n * - Dependency injection with Container\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing\n * - Keep route handlers thin, delegate to services\n * - Return appropriate HTTP status codes\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type {\n HandoffRequest,\n IExtensionSessionRegistry,\n SessionHeartbeatRequest,\n SessionRegistrationRequest,\n} from '../services/ExtensionSessionRegistry.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ExtensionTaskQueue, ExtensionTaskResult } from '../services/ExtensionTaskQueue.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport {\n TelemetryService,\n type ITelemetryService,\n resolveBrowserTelemetryConfig,\n} from '../services/TelemetryService.js';\n\n/**\n * Task request body from extension polling\n */\ninterface TaskPollResponse {\n task?: {\n id: string;\n tool: string;\n arguments: Record<string, unknown>;\n telemetry?: {\n traceId?: string;\n parentSpanId?: string;\n };\n };\n}\n\n/**\n * Result submission body from extension\n */\ninterface ResultSubmitRequest {\n taskId: string;\n success: boolean;\n result?: {\n content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n isError?: boolean;\n };\n error?: string;\n}\n\ninterface TabMappedRequest {\n pageId: string;\n tabId: number;\n}\n\ninterface RecordingArtifactRequest {\n browserId: string;\n videoBase64?: string;\n}\n\ninterface RecordingChunkRequest {\n browserId: string;\n chunkBase64: string;\n mimeType?: string;\n chunkIndex?: number;\n}\n\ninterface BrowserLogRequest {\n level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n message: string;\n attributes?: Record<string, unknown>;\n}\n\nconst DEBUG_EXTENSION_RECORDING_STDOUT = process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1';\n\nfunction emitExtensionRecordingDebug(message: string, details?: Record<string, unknown>): void {\n if (!DEBUG_EXTENSION_RECORDING_STDOUT) {\n return;\n }\n\n const payload = details ? ` ${JSON.stringify(details)}` : '';\n console.log(`[ExtensionRecordingDebug] ${message}${payload}`);\n}\n\n/**\n * Status response\n */\ninterface StatusResponse {\n connected: boolean;\n lastPollAt?: string;\n lastResultAt?: string;\n pendingTasks: number;\n queueSize: number;\n}\n\n/**\n * Create extension routes for Chrome extension communication\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono router\n */\nexport function createExtensionRoutes(container: Container): Hono {\n const router = new Hono();\n let telemetry: ITelemetryService;\n try {\n telemetry = container.get<ITelemetryService>(PLAYWRIGHT_TYPES.TelemetryService);\n } catch {\n telemetry = new TelemetryService();\n }\n\n router.get('/telemetry-config', (c) => {\n try {\n const requestOrigin = new URL(c.req.url).origin;\n return c.json(resolveBrowserTelemetryConfig(process.env, requestOrigin));\n } catch (error) {\n return c.json(\n {\n enabled: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/tasks\n * Extension polls for next task to execute\n */\n router.get('/tasks', (c) => {\n try {\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const task = taskQueue.getNextTask();\n\n if (!task) {\n return c.json<TaskPollResponse>({});\n }\n\n return c.json<TaskPollResponse>({\n task: {\n id: task.id,\n tool: task.tool,\n arguments: task.arguments,\n telemetry: task.telemetry,\n },\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/result\n * Extension submits task execution result\n */\n router.post('/result', async (c) => {\n try {\n const body = (await c.req.json()) as ResultSubmitRequest;\n\n if (!body.taskId) {\n return c.json(\n {\n success: false,\n error: 'Missing taskId in request body',\n },\n 400,\n );\n }\n\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n\n const result: ExtensionTaskResult = {\n taskId: body.taskId,\n success: body.success,\n result: body.result,\n error: body.error,\n };\n\n const task = taskQueue.submitResult(result);\n\n if (!task) {\n return c.json(\n {\n success: false,\n error: `Task ${body.taskId} not found or already completed`,\n },\n 404,\n );\n }\n\n if (task.browserId) {\n browserService.recordBrowserActivity(task.browserId, task.pageId);\n }\n\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/status\n * Connection health check and status\n */\n router.get('/status', (c) => {\n try {\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const status = taskQueue.getConnectionStatus();\n\n const response: StatusResponse = {\n connected: status.connected,\n lastPollAt: status.lastPollAt?.toISOString(),\n lastResultAt: status.lastResultAt?.toISOString(),\n pendingTasks: status.pendingTasks,\n queueSize: taskQueue.queueSize,\n };\n\n return c.json(response);\n } catch (error) {\n return c.json(\n {\n connected: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/register\n * Extension registers a stealth browser session\n */\n router.post('/register', async (c) => {\n try {\n const body = (await c.req.json()) as SessionRegistrationRequest;\n\n if (!body.browserId) {\n return c.json(\n {\n success: false,\n error: 'Missing browserId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.register(body);\n\n return c.json({\n success: true,\n session: {\n id: session.id,\n browserId: session.browserId,\n controlMode: session.controlMode,\n createdAt: session.createdAt.toISOString(),\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/heartbeat\n * Extension sends heartbeat to keep session alive\n */\n router.post('/heartbeat', async (c) => {\n try {\n const body = (await c.req.json()) as SessionHeartbeatRequest;\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.heartbeat(body);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n lastHeartbeatAt: session.lastHeartbeatAt.toISOString(),\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/tab-mapped', async (c) => {\n try {\n const body = (await c.req.json()) as TabMappedRequest;\n\n if (!body.pageId || typeof body.tabId !== 'number') {\n return c.json(\n {\n success: false,\n error: 'Missing pageId or tabId in request body',\n },\n 400,\n );\n }\n\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageEntry = pageRegistry.get(body.pageId);\n\n if (!pageEntry) {\n return c.json(\n {\n success: false,\n error: `Page ${body.pageId} not found`,\n },\n 404,\n );\n }\n\n pageEntry.extensionTabId = body.tabId;\n browserService.recordBrowserActivity(pageEntry.browserId, body.pageId);\n\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/recording', async (c) => {\n try {\n const body = (await c.req.json()) as RecordingArtifactRequest;\n\n if (!body.browserId) {\n return c.json(\n {\n success: false,\n error: 'Missing browserId in request body',\n },\n 400,\n );\n }\n\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const persisted = await browserService.persistExtensionRecordingArtifact(body.browserId, body.videoBase64);\n emitExtensionRecordingDebug('artifact received', {\n browserId: body.browserId,\n videoBase64Size: body.videoBase64?.length ?? 0,\n persisted,\n });\n\n if (!persisted) {\n return c.json(\n {\n success: false,\n error: `Browser \"${body.browserId}\" has no active recording target`,\n },\n 404,\n );\n }\n\n browserService.recordBrowserActivity(body.browserId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/recording/chunk', async (c) => {\n try {\n const body = (await c.req.json()) as RecordingChunkRequest;\n\n if (!body.browserId || !body.chunkBase64) {\n return c.json(\n {\n success: false,\n error: 'Missing browserId or chunkBase64 in request body',\n },\n 400,\n );\n }\n\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const persisted = await browserService.persistExtensionRecordingChunk(body.browserId, body.chunkBase64);\n emitExtensionRecordingDebug('chunk received', {\n browserId: body.browserId,\n chunkIndex: body.chunkIndex,\n mimeType: body.mimeType,\n persisted: !!persisted,\n chunkBase64Size: body.chunkBase64.length,\n });\n\n if (!persisted) {\n return c.json(\n {\n success: false,\n error: `Browser \"${body.browserId}\" has no active recording target`,\n },\n 404,\n );\n }\n\n telemetry.log('debug', 'extension recording chunk received', {\n attributes: {\n 'browse_tool.extension.recording.chunk_received': true,\n 'browse_tool.browser.id': body.browserId,\n 'browse_tool.recording.chunk_bytes': persisted.chunkBytes,\n 'browse_tool.recording.total_bytes': persisted.totalBytes,\n 'browse_tool.recording.chunk_count': persisted.chunkCount,\n ...(typeof body.chunkIndex === 'number' ? { 'browse_tool.recording.chunk_index': body.chunkIndex } : {}),\n ...(typeof body.mimeType === 'string' ? { 'browse_tool.recording.mime_type': body.mimeType } : {}),\n },\n });\n\n browserService.recordBrowserActivity(body.browserId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n router.post('/browser-log', async (c) => {\n try {\n const body = (await c.req.json()) as BrowserLogRequest;\n\n if (!body.message || typeof body.message !== 'string') {\n return c.json(\n {\n success: false,\n error: 'Missing message in request body',\n },\n 400,\n );\n }\n\n telemetry.log(body.level ?? 'info', body.message, {\n attributes: {\n 'browse_tool.extension.log_relay': true,\n ...(typeof body.attributes === 'object' && body.attributes !== null ? body.attributes : {}),\n },\n });\n emitExtensionRecordingDebug('browser log relayed', {\n level: body.level ?? 'info',\n message: body.message,\n attributes: body.attributes,\n });\n\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/handoff\n * Request handoff from spec to AI control\n */\n router.post('/handoff', async (c) => {\n try {\n const body = (await c.req.json()) as HandoffRequest;\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.requestHandoff(body);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n message: 'Handoff requested. AI will take control when ready.',\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/handoff/acknowledge\n * AI acknowledges handoff and takes control\n */\n router.post('/handoff/acknowledge', async (c) => {\n try {\n const body = (await c.req.json()) as { sessionId: string };\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.acknowledgeHandoff(body.sessionId);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found or no handoff pending`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n message: 'Handoff acknowledged. AI now has control.',\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/sessions\n * List all active extension sessions\n */\n router.get('/sessions', (c) => {\n try {\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const sessions = sessionRegistry.listSessions();\n\n return c.json({\n sessions: sessions.map((s) => ({\n id: s.id,\n browserId: s.browserId,\n tabId: s.tabId,\n currentUrl: s.currentUrl,\n controlMode: s.controlMode,\n activeSpecPath: s.activeSpecPath,\n handoffRequested: s.handoffRequested,\n createdAt: s.createdAt.toISOString(),\n lastHeartbeatAt: s.lastHeartbeatAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n return router;\n}\n","/**\n * Chrome Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - HTTP server pattern for Chrome extension polling\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n */\n\nimport { serve } from '@hono/node-server';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { Command } from 'commander';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createExtensionRoutes } from '../server/extension-routes.js';\nimport { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { ExtensionToolDelegator } from '../services/ExtensionToolDelegator.js';\nimport type { ToolDefinition } from '../types/index.js';\n\nexport interface ChromeServeOptions {\n port: number;\n verbose: boolean;\n waitForExtension: boolean;\n}\n\n/**\n * Create extension-specific container module\n */\nfunction createExtensionModule(): ContainerModule {\n return new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(PLAYWRIGHT_TYPES.ExtensionTaskQueue).to(ExtensionTaskQueue).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionToolDelegator).to(ExtensionToolDelegator).inSingletonScope();\n });\n}\n\n/**\n * Create MCP server that delegates to Chrome extension\n */\nfunction createExtensionMcpServer(delegator: ExtensionToolDelegator): Server {\n const server = new Server(\n {\n name: 'browse-tool-chrome',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const supportedTools = delegator.getSupportedTools();\n\n const toolDefinitions: ToolDefinition[] = supportedTools.map((name) => ({\n name,\n description: `Browser automation tool (Chrome extension mode): ${name}`,\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: true,\n },\n }));\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: toolDefinitions };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n return await delegator.executeTool(name, args || {});\n });\n\n return server;\n}\n\nexport const chromeServeCommand = new Command('chrome-serve')\n .description(\n '[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation',\n )\n .option('-p, --port <port>', 'HTTP server port for extension polling', '3200')\n .option('-v, --verbose', 'Enable verbose output', false)\n .option('--wait-for-extension', 'Wait for extension to connect before accepting MCP requests', false)\n .action(async (options: ChromeServeOptions) => {\n // Deprecation warning\n console.error('');\n console.error('╔════════════════════════════════════════════════════════════════╗');\n console.error('║ DEPRECATED: chrome-serve is deprecated. ║');\n console.error('║ Use mcp-serve instead - extension routes are now ║');\n console.error('║ automatically available in the HTTP server at /extension/*. ║');\n console.error('╚════════════════════════════════════════════════════════════════╝');\n console.error('');\n\n try {\n const port = typeof options.port === 'string' ? Number.parseInt(options.port, 10) : options.port;\n\n if (options.verbose) {\n console.error('Chrome Extension MCP Server starting...');\n console.error(` HTTP Port: ${port}`);\n console.error(` Wait for extension: ${options.waitForExtension}`);\n }\n\n // Create container with extension services\n const container = new Container({ defaultScope: 'Singleton' });\n container.load(createExtensionModule());\n\n // Get services\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const delegator = container.get<ExtensionToolDelegator>(PLAYWRIGHT_TYPES.ExtensionToolDelegator);\n\n // Create HTTP server for extension polling\n const app = new Hono();\n\n app.use(\n '*',\n cors({\n origin: (origin) => origin ?? null,\n credentials: true,\n allowMethods: ['GET', 'POST', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Accept'],\n }),\n );\n\n // Mount extension routes\n const extensionRoutes = createExtensionRoutes(container);\n app.route('/extension', extensionRoutes);\n\n // Health check\n app.get('/health', (c) => {\n const status = taskQueue.getConnectionStatus();\n return c.json({\n status: 'healthy',\n service: 'browse-tool-chrome',\n extension: status,\n });\n });\n\n // Start HTTP server\n const httpServer = serve({\n fetch: app.fetch,\n port,\n });\n\n // Handle server errors (e.g., port in use)\n httpServer.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n console.error(`Error [PORT_IN_USE]: Port ${port} is already in use.`);\n console.error('Recovery: Try a different port with --port <port>');\n } else {\n console.error(`Error [SERVER_ERROR]: HTTP server error: ${error.message}`);\n }\n process.exit(1);\n });\n\n console.error(`HTTP server started on port ${port}`);\n console.error('Waiting for Chrome extension to connect...');\n console.error(`Extension should poll: http://localhost:${port}/extension/tasks`);\n\n // Wait for extension if requested\n if (options.waitForExtension) {\n console.error('Waiting for extension connection before starting MCP...');\n await new Promise<void>((resolve) => {\n const checkInterval = setInterval(() => {\n const status = taskQueue.getConnectionStatus();\n if (status.connected) {\n clearInterval(checkInterval);\n console.error('Extension connected!');\n resolve();\n }\n }, 500);\n });\n }\n\n // Create MCP server with extension delegation\n const mcpServer = createExtensionMcpServer(delegator);\n\n // Start stdio transport\n const transport = new StdioServerTransport();\n await mcpServer.connect(transport);\n\n console.error('Chrome extension MCP server started on stdio');\n\n const shutdown = async (signal: string) => {\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n taskQueue.clearAllTasks('Server shutting down');\n await transport.close();\n httpServer.close();\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${errorMessage}`);\n console.error('Recovery: Check that the port is available and try again.');\n process.exit(1);\n }\n });\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport { z } from 'zod';\n\nconst CONFIG_ENV_VAR = 'BROWSE_TOOL_CONFIG';\nconst CONFIG_DIR_NAME = '.browse-tool';\nconst CONFIG_FILE_NAME = 'config.json';\nconst OUTPUT_FORMATS = ['json', 'text', 'quiet'] as const;\nconst DEFAULT_CONFIG: BrowseToolConfig = {\n commands: {},\n tools: {},\n};\n\nconst toolDefaultsSchema = z.record(z.string(), z.unknown());\n\nconst commandDefaultsSchema = z.object({\n mcpServe: z\n .object({\n type: z.string().optional(),\n browser: z.string().optional(),\n headless: z.boolean().optional(),\n profile: z.string().optional(),\n mode: z.string().optional(),\n host: z.string().optional(),\n port: z.coerce.number().int().positive().optional(),\n tags: z.string().optional(),\n exclude: z.string().optional(),\n customTools: z.string().optional(),\n snippetsDir: z.string().optional(),\n registryPath: z.string().optional(),\n registryDir: z.string().optional(),\n pidsDir: z.string().optional(),\n profilesDir: z.string().optional(),\n })\n .partial()\n .optional(),\n httpServe: z\n .object({\n port: z.coerce.number().int().positive().optional(),\n headless: z.boolean().optional(),\n idleTimeout: z.coerce.number().positive().optional(),\n host: z.string().optional(),\n registryDir: z.string().optional(),\n registryPath: z.string().optional(),\n pidsDir: z.string().optional(),\n profilesDir: z.string().optional(),\n snippetsDir: z.string().optional(),\n })\n .partial()\n .optional(),\n exec: z\n .object({\n format: z.enum(OUTPUT_FORMATS).optional(),\n color: z.boolean().optional(),\n port: z.coerce.number().int().positive().optional(),\n })\n .partial()\n .optional(),\n tools: z\n .object({\n format: z.enum(OUTPUT_FORMATS).optional(),\n color: z.boolean().optional(),\n port: z.coerce.number().int().positive().optional(),\n })\n .partial()\n .optional(),\n status: z.record(z.string(), z.unknown()).optional(),\n stop: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst browseToolConfigSchema = z.object({\n commands: commandDefaultsSchema.default({}),\n tools: z.record(z.string(), toolDefaultsSchema).default({}),\n});\n\nexport type BrowseToolConfig = z.infer<typeof browseToolConfigSchema>;\n\nexport interface CliRuntimeContext {\n config: BrowseToolConfig;\n configPath?: string;\n}\n\nexport interface CliRuntimeOptions {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n homeDir?: string;\n}\n\nlet runtimeContext: CliRuntimeContext = {\n config: DEFAULT_CONFIG,\n};\n\nfunction extractExplicitConfigPath(argv: string[]): string | undefined {\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === '--config') {\n return argv[i + 1];\n }\n if (arg.startsWith('--config=')) {\n return arg.slice('--config='.length);\n }\n }\n return undefined;\n}\n\nfunction resolveConfigCandidatePath(candidatePath: string): string {\n const resolvedPath = path.resolve(candidatePath);\n if (!existsSync(resolvedPath)) {\n throw new Error(`Config path not found: ${resolvedPath}`);\n }\n\n if (statSync(resolvedPath).isDirectory()) {\n return path.join(resolvedPath, CONFIG_FILE_NAME);\n }\n\n return resolvedPath;\n}\n\nfunction findConfigPath(argv: string[], options: CliRuntimeOptions): string | undefined {\n const env = options.env ?? process.env;\n const cwd = options.cwd ?? process.cwd();\n const homeDir = options.homeDir ?? homedir();\n\n const explicitPath = extractExplicitConfigPath(argv);\n if (explicitPath) {\n return resolveConfigCandidatePath(explicitPath);\n }\n\n if (env[CONFIG_ENV_VAR]) {\n return resolveConfigCandidatePath(env[CONFIG_ENV_VAR] as string);\n }\n\n const localConfigPath = path.join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n if (existsSync(localConfigPath)) {\n return localConfigPath;\n }\n\n const homeConfigPath = path.join(homeDir, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n if (existsSync(homeConfigPath)) {\n return homeConfigPath;\n }\n\n return undefined;\n}\n\nfunction readConfigFile(configPath: string): BrowseToolConfig {\n const content = readFileSync(configPath, 'utf8');\n const parsed = JSON.parse(content) as unknown;\n return browseToolConfigSchema.parse(parsed);\n}\n\nexport function loadCliRuntimeContext(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n const configPath = findConfigPath(argv, options);\n if (!configPath) {\n return { config: DEFAULT_CONFIG };\n }\n\n return {\n configPath,\n config: readConfigFile(configPath),\n };\n}\n\nexport function initializeCliRuntime(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n runtimeContext = loadCliRuntimeContext(argv, options);\n return runtimeContext;\n}\n\nexport function getCliRuntimeContext(): CliRuntimeContext {\n return runtimeContext;\n}\n\nexport function setCliRuntimeContextForTesting(context: CliRuntimeContext): void {\n runtimeContext = context;\n}\n\nexport function getCommandConfig<T extends Record<string, unknown>>(\n commandName: keyof BrowseToolConfig['commands'],\n): T {\n const commands = runtimeContext.config.commands ?? {};\n return (commands[commandName] ?? {}) as T;\n}\n\nexport function getToolConfig(toolName: string): Record<string, unknown> {\n return runtimeContext.config.tools?.[toolName] ?? {};\n}\n\nexport function resolveConfiguredOption<T>(\n command: Command,\n optionName: string,\n currentValue: T,\n configValue?: T,\n envValue?: T,\n): T {\n const source = command.getOptionValueSourceWithGlobals(optionName);\n if (source !== undefined && source !== 'default' && source !== 'implied') {\n return currentValue;\n }\n\n if (envValue !== undefined) {\n return envValue;\n }\n\n if (configValue !== undefined) {\n return configValue;\n }\n\n return currentValue;\n}\n","/**\n * HTTP Client Utility for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Service pattern for HTTP communication\n * - Dependency injection via container\n * - Error handling with descriptive messages\n *\n * CODING STANDARDS:\n * - Use async/await for all HTTP operations\n * - Return typed responses\n * - Handle connection errors gracefully\n *\n * AVOID:\n * - Hardcoded URLs (use server discovery)\n * - Missing error handling\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createMcpContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport type { ToolDefinition } from '../types/index.js';\n\n/**\n * Response from /tools endpoint\n */\ninterface ToolsResponse {\n tools: ToolDefinition[];\n error?: string;\n}\n\nexport interface CustomToolDefinition {\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: ToolDefinition['inputSchema'];\n capabilities?: Record<string, unknown>;\n}\n\ninterface CustomToolsResponse {\n tools: CustomToolDefinition[];\n error?: string;\n}\n\n/**\n * Browser info response from /browsers\n */\nexport interface BrowserInfo {\n id: string;\n profileName?: string;\n pageIds: string[];\n currentPageId?: string;\n createdAt: string;\n}\n\ninterface BrowsersResponse {\n browsers: BrowserInfo[];\n error?: string;\n}\n\n/**\n * Response from /execute endpoint\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: CallToolResult;\n error?: string;\n}\n\n/**\n * Client options\n */\nexport interface ToolClientOptions {\n /** Port to use for HTTP server */\n port?: number;\n /** When true, only connect to or spawn the exact requested port */\n exactPort?: boolean;\n /** Timeout for HTTP requests in milliseconds */\n timeout?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60000;\n\n/**\n * HTTP client for executing tools via the browse-tool HTTP server.\n * Handles server discovery/startup and provides typed methods for tool operations.\n */\nexport class ToolClient {\n private readonly port: number;\n private readonly exactPort: boolean;\n private readonly timeout: number;\n private serverPort: number | null = null;\n\n constructor(options: ToolClientOptions = {}) {\n this.port = options.port ?? 3200;\n this.exactPort = options.exactPort ?? false;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Ensure HTTP server is running and return its port\n */\n private async ensureServer(): Promise<number> {\n if (this.serverPort !== null) {\n return this.serverPort;\n }\n\n const container = createMcpContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n const status = await httpServerManager.ensureRunning(this.port, { exactPort: this.exactPort });\n\n if (!status.running || !status.port) {\n throw new Error(status.error ?? 'Failed to start HTTP server. Try running \"browse-tool http-serve\" manually.');\n }\n\n this.serverPort = status.port;\n return status.port;\n }\n\n /**\n * Get base URL for HTTP server\n */\n private async getBaseUrl(): Promise<string> {\n const port = await this.ensureServer();\n return `http://localhost:${port}`;\n }\n\n /**\n * List all available tools from the HTTP server\n */\n async listTools(): Promise<ToolDefinition[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${baseUrl}/tools`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as ToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n return data.tools;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * List custom tools exposed by the HTTP server for a given directory\n */\n async listCustomTools(directory: string): Promise<CustomToolDefinition[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const url = new URL('/custom-tools', baseUrl);\n url.searchParams.set('dir', directory);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as CustomToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n return data.tools;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * List active browsers from the HTTP server\n */\n async listBrowsers(): Promise<BrowserInfo[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${baseUrl}/browsers`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as BrowsersResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n return data.browsers;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Execute a tool with the given arguments\n */\n async execute(tool: string, args: Record<string, unknown>): Promise<CallToolResult> {\n const baseUrl = await this.getBaseUrl();\n // run_spec can execute long-running suites. Do not enforce client-side request timeout.\n const timeoutMs = tool === 'run_spec' ? undefined : this.timeout;\n const controller = new AbortController();\n const timeoutId = timeoutMs !== undefined ? setTimeout(() => controller.abort(), timeoutMs) : undefined;\n\n try {\n const response = await fetch(`${baseUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n tool,\n arguments: args,\n }),\n signal: timeoutMs !== undefined ? controller.signal : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n return {\n content: [{ type: 'text', text: data.error ?? 'Unknown error' }],\n isError: true,\n };\n }\n\n return (\n data.result ?? {\n content: [{ type: 'text', text: 'No result returned' }],\n }\n );\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n /**\n * Execute a custom tool exposed by the HTTP server for a given directory\n */\n async executeCustomTool(directory: string, tool: string, args: Record<string, unknown>): Promise<CallToolResult> {\n const baseUrl = await this.getBaseUrl();\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const url = new URL('/custom-tools', baseUrl);\n url.searchParams.set('dir', directory);\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n tool,\n arguments: args,\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n return {\n content: [{ type: 'text', text: data.error ?? 'Unknown error' }],\n isError: true,\n };\n }\n\n return (\n data.result ?? {\n content: [{ type: 'text', text: 'No result returned' }],\n }\n );\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Wrap connection errors with helpful recovery messages\n */\n private wrapConnectionError(error: unknown): Error {\n if (error instanceof Error) {\n if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch failed')) {\n return new Error(\n `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: ${error.message}`,\n { cause: error },\n );\n }\n return error;\n }\n return new Error(String(error), { cause: error });\n }\n}\n\n/**\n * Create a new ToolClient instance\n */\nexport function createToolClient(options?: ToolClientOptions): ToolClient {\n return new ToolClient(options);\n}\n","/**\n * Output Formatter Utilities for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Pure functions with no side effects\n * - Single responsibility per function\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - External dependencies (keep utilities pure)\n */\n\nimport type { CallToolResult, ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Output format options\n */\nexport type OutputFormat = 'json' | 'text' | 'quiet';\n\n/**\n * Formatter options\n */\nexport interface FormatterOptions {\n format: OutputFormat;\n color: boolean;\n}\n\n/**\n * ANSI color codes for terminal output\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n magenta: '\\x1b[35m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m',\n} as const;\n\n/**\n * Apply color to text if color is enabled\n */\nexport function colorize(text: string, color: keyof typeof COLORS, useColor: boolean): string {\n if (!useColor) {\n return text;\n }\n return `${COLORS[color]}${text}${COLORS.reset}`;\n}\n\n/**\n * Format a CallToolResult for CLI output\n */\nexport function formatToolResult(result: CallToolResult, options: FormatterOptions): string {\n const { format, color } = options;\n\n if (format === 'quiet') {\n return '';\n }\n\n if (format === 'json') {\n return formatAsJson(result);\n }\n\n return formatAsText(result, color);\n}\n\n/**\n * Format result as JSON\n */\nexport function formatAsJson(result: CallToolResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Format result as human-readable text\n */\nexport function formatAsText(result: CallToolResult, useColor: boolean): string {\n const lines: string[] = [];\n\n if (result.isError) {\n lines.push(colorize('Error:', 'red', useColor));\n }\n\n for (const content of result.content) {\n if (content.type === 'text') {\n lines.push(formatTextContent(content as TextContent, useColor));\n } else if (content.type === 'image') {\n lines.push(formatImageContent(content as ImageContent, useColor));\n } else {\n lines.push(colorize(`[Unknown content type: ${content.type}]`, 'yellow', useColor));\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format text content\n */\nexport function formatTextContent(content: TextContent, useColor: boolean): string {\n const text = content.text;\n\n // Try to parse as JSON for pretty printing\n try {\n const parsed = JSON.parse(text);\n return formatParsedJson(parsed, useColor);\n } catch {\n // Not JSON, return as-is\n return text;\n }\n}\n\n/**\n * Format parsed JSON object with optional coloring\n */\nexport function formatParsedJson(data: unknown, useColor: boolean): string {\n if (typeof data !== 'object' || data === null) {\n return String(data);\n }\n\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(data)) {\n const coloredKey = colorize(key, 'cyan', useColor);\n const formattedValue = formatValue(value, useColor);\n lines.push(`${coloredKey}: ${formattedValue}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format a value with type-appropriate coloring\n */\nexport function formatValue(value: unknown, useColor: boolean): string {\n if (value === null) {\n return colorize('null', 'gray', useColor);\n }\n\n if (value === undefined) {\n return colorize('undefined', 'gray', useColor);\n }\n\n if (typeof value === 'boolean') {\n return colorize(String(value), value ? 'green' : 'red', useColor);\n }\n\n if (typeof value === 'number') {\n return colorize(String(value), 'yellow', useColor);\n }\n\n if (typeof value === 'string') {\n // Check if it's a URL\n if (value.startsWith('http://') || value.startsWith('https://')) {\n return colorize(value, 'blue', useColor);\n }\n return value;\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return colorize('[]', 'gray', useColor);\n }\n return JSON.stringify(value, null, 2);\n }\n\n if (typeof value === 'object') {\n return JSON.stringify(value, null, 2);\n }\n\n return String(value);\n}\n\n/**\n * Format image content\n */\nexport function formatImageContent(content: ImageContent, useColor: boolean): string {\n const { mimeType, data } = content;\n const sizeKb = Math.round((data.length * 3) / 4 / 1024);\n\n return colorize(`[Image: ${mimeType}, ~${sizeKb}KB base64 data]`, 'magenta', useColor);\n}\n\n/**\n * Format an error for CLI output\n */\nexport function formatError(error: Error | string, useColor: boolean): string {\n const message = error instanceof Error ? error.message : error;\n return colorize(`Error: ${message}`, 'red', useColor);\n}\n\n/**\n * Format a success message for CLI output\n */\nexport function formatSuccess(message: string, useColor: boolean): string {\n return colorize(`✓ ${message}`, 'green', useColor);\n}\n\n/**\n * Format a warning message for CLI output\n */\nexport function formatWarning(message: string, useColor: boolean): string {\n return colorize(`⚠ ${message}`, 'yellow', useColor);\n}\n\n/**\n * Format a list of tools for help output\n */\nexport function formatToolList(tools: Array<{ name: string; description: string }>, useColor: boolean): string {\n const maxNameLength = Math.max(...tools.map((t) => t.name.length));\n const lines: string[] = [];\n\n for (const tool of tools) {\n const paddedName = tool.name.padEnd(maxNameLength);\n const coloredName = colorize(paddedName, 'cyan', useColor);\n lines.push(` ${coloredName} ${tool.description}`);\n }\n\n return lines.join('\\n');\n}\n","import { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createToolClient } from '../utils/httpClient.js';\nimport { DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport {\n type FormatterOptions,\n type OutputFormat,\n formatError,\n formatToolList,\n formatToolResult,\n} from '../utils/outputFormatter.js';\n\ninterface CustomToolsCommandOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\nfunction resolveCommonOptions(\n command: Command,\n options: CustomToolsCommandOptions,\n): {\n port: string;\n formatterOptions: FormatterOptions;\n} {\n const commandDefaults = getCommandConfig<{\n format?: OutputFormat;\n color?: boolean;\n port?: number;\n }>('tools');\n\n const format = resolveConfiguredOption(command, 'format', options.format as OutputFormat, commandDefaults.format);\n const color = resolveConfiguredOption(command, 'color', options.color, commandDefaults.color);\n const port = resolveConfiguredOption(\n command,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n\n return {\n port,\n formatterOptions: {\n format,\n color,\n },\n };\n}\n\nexport const listCustomToolsCommand = new Command('list-custom-tools')\n .description('List custom tools from a tools directory')\n .argument('<dir>', 'Path to the tools directory containing tools.yaml')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n .action(async function (this: Command, dir: string, options: CustomToolsCommandOptions) {\n const { port, formatterOptions } = resolveCommonOptions(this, options);\n\n try {\n const client = createToolClient({\n port: Number.parseInt(port, 10),\n });\n\n const tools = await client.listCustomTools(dir);\n const output =\n formatterOptions.format === 'text'\n ? formatToolList(tools, formatterOptions.color)\n : formatterOptions.format === 'quiet'\n ? ''\n : JSON.stringify(tools, null, 2);\n\n if (output) {\n console.log(output);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n\nexport const execCustomToolCommand = new Command('exec-custom-tool')\n .description('Execute a custom tool from a tools directory')\n .argument('<dir>', 'Path to the tools directory containing tools.yaml')\n .argument('<tool>', 'Custom tool name to execute')\n .argument('[args]', 'JSON arguments for the tool', '{}')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n .action(async function (\n this: Command,\n dir: string,\n tool: string,\n argsJson: string,\n options: CustomToolsCommandOptions,\n ) {\n const { port, formatterOptions } = resolveCommonOptions(this, options);\n\n try {\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsJson);\n } catch {\n console.error(formatError(`Invalid JSON arguments: ${argsJson}`, formatterOptions.color));\n process.exit(1);\n return;\n }\n\n const client = createToolClient({\n port: Number.parseInt(port, 10),\n });\n\n const result = await client.executeCustomTool(dir, tool, args);\n const output = formatToolResult(result, formatterOptions);\n if (output) {\n console.log(output);\n }\n\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n\nexport const customToolsCommand = new Command('custom-tools')\n .description('List and execute custom tools through the HTTP server')\n .addCommand(listCustomToolsCommand)\n .addCommand(execCustomToolCommand);\n","import { Command } from 'commander';\nimport chromeForTestingConfig from '../config/chrome-for-testing.json';\nimport {\n buildDockerChromeForTestingImage,\n resolveChromeForTestingArchivePlatform,\n} from '../utils/dockerChromeForTesting.js';\n\ninterface DockerBuildCftOptions {\n cftVersion?: string;\n image?: string;\n platform?: string;\n}\n\nexport const dockerBuildCftCommand = new Command('docker-build-cft')\n .description('Build the Chrome for Testing Docker image used by vm mode')\n .option('--cft-version <version>', 'Chrome for Testing version to build', chromeForTestingConfig.version)\n .option('--image <image>', 'Docker image tag to produce', chromeForTestingConfig.dockerImage)\n .option('--platform <platform>', 'Docker target platform', chromeForTestingConfig.dockerPlatform)\n .action(async (options: DockerBuildCftOptions) => {\n try {\n const result = await buildDockerChromeForTestingImage({\n version: options.cftVersion,\n image: options.image,\n platform: options.platform,\n stdio: 'inherit',\n });\n\n console.log(`Built Docker image ${result.image}`);\n console.log(` Version: ${result.version}`);\n console.log(` Platform: ${result.platform}`);\n console.log(` Archive: ${resolveChromeForTestingArchivePlatform(result.platform)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n });\n","/**\n * Exec Command\n *\n * Execute a tool directly with JSON arguments. Power user command for raw API access.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() and .argument() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createToolClient } from '../utils/httpClient.js';\nimport { DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport { type FormatterOptions, type OutputFormat, formatError, formatToolResult } from '../utils/outputFormatter.js';\n\ninterface ExecOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\n/**\n * Execute a tool directly with JSON arguments\n */\nexport const execCommand = new Command('exec')\n .description('Execute a tool directly with JSON arguments')\n .argument('<tool>', 'Tool name to execute (e.g., browser_launch)')\n .argument('[args]', 'JSON arguments for the tool', '{}')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n .action(async function (this: Command, tool: string, argsJson: string, options: ExecOptions) {\n const commandDefaults = getCommandConfig<{\n format?: OutputFormat;\n color?: boolean;\n port?: number;\n }>('exec');\n const format = resolveConfiguredOption(this, 'format', options.format as OutputFormat, commandDefaults.format);\n const color = resolveConfiguredOption(this, 'color', options.color, commandDefaults.color);\n const port = resolveConfiguredOption(\n this,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n const portSource = this.getOptionValueSourceWithGlobals('port');\n const exactPort = portSource !== undefined && portSource !== 'default' && portSource !== 'implied';\n const formatterOptions: FormatterOptions = {\n format,\n color,\n };\n\n try {\n // Parse JSON arguments\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsJson);\n } catch {\n console.error(formatError(`Invalid JSON arguments: ${argsJson}`, formatterOptions.color));\n process.exit(1);\n }\n\n // Create client and execute\n const client = createToolClient({\n port: Number.parseInt(port, 10),\n exactPort,\n });\n\n const result = await client.execute(tool, args);\n\n // Format and output result\n const output = formatToolResult(result, formatterOptions);\n if (output) {\n console.log(output);\n }\n\n // Exit with error code if tool failed\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n","import { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { stripTypeScriptTypes } from 'node:module';\nimport type { Attributes } from '@opentelemetry/api';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport type { IPageRegistry, PageEntry } from './PageRegistry.js';\nimport type { BrowserMode, IBrowserService } from './BrowserService.js';\nimport { ExtensionPageProxy, type IExtensionPageProxy } from './ExtensionPageProxy.js';\nimport type { ExtensionTaskQueue } from './ExtensionTaskQueue.js';\nimport { TelemetryService, type ITelemetryService } from './TelemetryService.js';\n\nconst MANIFEST_FILE = 'tools.yaml';\nconst REQUIRED_PAGE_ID_FIELD = 'pageId';\nconst OPTIONAL_BROWSER_ID_FIELD = 'browserId';\n\nconst ToolCapabilitiesSchema = z.record(z.string(), z.unknown());\n\nconst ToolInputSchemaSchema = z\n .object({\n type: z.literal('object'),\n properties: z.record(z.string(), z.unknown()).optional(),\n required: z.array(z.string()).optional(),\n additionalProperties: z.boolean().optional(),\n })\n .passthrough();\n\nconst CustomToolManifestEntrySchema = z.object({\n name: z.string().min(1),\n description: z.string().min(1),\n script: z.string().min(1),\n suggestionActions: z.string().min(1).optional(),\n capabilities: ToolCapabilitiesSchema,\n inputSchema: ToolInputSchemaSchema,\n});\n\nconst CustomToolManifestSchema = z.object({\n tools: z.array(CustomToolManifestEntrySchema),\n});\n\ntype CustomToolManifestEntry = z.infer<typeof CustomToolManifestEntrySchema>;\n\nexport interface CustomToolDefinition {\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: z.infer<typeof ToolInputSchemaSchema>;\n capabilities: z.infer<typeof ToolCapabilitiesSchema>;\n}\n\ninterface LoadedCustomTool extends CustomToolDefinition {\n scriptPath: string;\n execute?: CustomToolHandler;\n}\n\ntype CustomToolPage = NonNullable<PageEntry['page']> | IExtensionPageProxy;\n\nexport interface CustomToolBrowserPageSummary {\n pageId: string;\n url: string;\n title: string;\n active: boolean;\n}\n\nexport interface CustomToolBrowserPageHandle extends CustomToolBrowserPageSummary {\n page: CustomToolPage;\n}\n\nexport interface CustomToolBrowser {\n readonly browserId: string;\n readonly mode: BrowserMode;\n listPages(): Promise<CustomToolBrowserPageSummary[]>;\n getPage(pageId: string): Promise<CustomToolBrowserPageHandle>;\n getCurrentPage(): Promise<CustomToolBrowserPageHandle>;\n newPage(options?: { url?: string; setAsCurrent?: boolean }): Promise<CustomToolBrowserPageHandle>;\n}\n\nexport interface CustomToolLogOptions {\n attributes?: Record<string, unknown>;\n exception?: unknown;\n}\n\nexport interface CustomToolLogger {\n getTraceContext(): { traceId?: string; spanId?: string };\n trace(message: string, options?: CustomToolLogOptions): void;\n debug(message: string, options?: CustomToolLogOptions): void;\n info(message: string, options?: CustomToolLogOptions): void;\n warn(message: string, options?: CustomToolLogOptions): void;\n error(message: string, options?: CustomToolLogOptions): void;\n fatal(message: string, options?: CustomToolLogOptions): void;\n}\n\ntype CustomToolHandler = (context: {\n page: CustomToolPage;\n browser: CustomToolBrowser;\n input: Record<string, unknown>;\n logger: CustomToolLogger;\n}) => unknown;\n\ninterface LoadedCustomToolModule {\n run?: unknown;\n default?: unknown;\n}\n\ninterface YamlLine {\n indent: number;\n text: string;\n}\n\nconst CUSTOM_TOOL_DEBUG_ENABLED = process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS === '1';\nconst CUSTOM_TOOL_PAGE_METADATA_WAIT_TIMEOUT_MS = 1500;\nconst CUSTOM_TOOL_PAGE_METADATA_WAIT_POLL_MS = 50;\n\nfunction summarizeValue(value: unknown): string {\n return JSON.stringify(value, (_key, current) => {\n if (typeof current === 'string' && current.length > 240) {\n return `${current.slice(0, 240)}...<trimmed>`;\n }\n return current;\n });\n}\n\nfunction debugCustomTool(message: string, details?: Record<string, unknown>): void {\n if (!CUSTOM_TOOL_DEBUG_ENABLED) {\n return;\n }\n\n if (details) {\n console.error(`[CustomToolService] ${message}`, details);\n return;\n }\n\n console.error(`[CustomToolService] ${message}`);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isCallToolResult(value: unknown): value is CallToolResult {\n return isPlainObject(value) && Array.isArray(value.content);\n}\n\nfunction normalizeScalar(rawValue: string): unknown {\n const value = rawValue.trim();\n if (value === '') {\n return '';\n }\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith('[') && value.endsWith(']'))) {\n return JSON.parse(value);\n }\n if (value.startsWith('{') && value.endsWith('}')) {\n return JSON.parse(value);\n }\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n return value.slice(1, -1);\n }\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n if (value === 'null') {\n return null;\n }\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return Number(value);\n }\n return value;\n}\n\nfunction toAttributes(input: Record<string, unknown> | undefined): Attributes | undefined {\n if (!input || Object.keys(input).length === 0) {\n return undefined;\n }\n\n const attributes: Attributes = {};\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined || value === null) {\n continue;\n }\n\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n attributes[key] = value;\n continue;\n }\n\n attributes[key] = JSON.stringify(value);\n }\n\n return Object.keys(attributes).length > 0 ? attributes : undefined;\n}\n\nfunction toYamlLines(input: string): YamlLine[] {\n return input\n .replace(/^\\uFEFF/, '')\n .split(/\\r?\\n/)\n .map((line) => {\n if (line.includes('\\t')) {\n throw new Error('Tab indentation is not supported in tools.yaml');\n }\n\n const withoutComment = line.replace(/\\s+#.*$/, '');\n if (withoutComment.trim().length === 0) {\n return null;\n }\n\n const indent = withoutComment.match(/^ */)?.[0].length ?? 0;\n return {\n indent,\n text: withoutComment.trim(),\n };\n })\n .filter((line): line is YamlLine => line !== null);\n}\n\nfunction parseYamlValue(lines: YamlLine[], startIndex: number, indent: number): [unknown, number] {\n const line = lines[startIndex];\n if (!line || line.indent !== indent) {\n throw new Error(`Invalid indentation in tools.yaml at line ${startIndex + 1}`);\n }\n\n if (line.text.startsWith('-')) {\n return parseYamlArray(lines, startIndex, indent);\n }\n\n return parseYamlObject(lines, startIndex, indent);\n}\n\nfunction parseYamlArray(lines: YamlLine[], startIndex: number, indent: number): [unknown[], number] {\n const result: unknown[] = [];\n let index = startIndex;\n\n while (index < lines.length) {\n const line = lines[index];\n if (line.indent < indent) {\n break;\n }\n if (line.indent !== indent || !line.text.startsWith('-')) {\n break;\n }\n\n const itemText = line.text.slice(1).trim();\n if (itemText === '') {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= indent) {\n result.push(null);\n index += 1;\n continue;\n }\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n result.push(nestedValue);\n index = nextIndex;\n continue;\n }\n\n if (itemText.includes(':')) {\n const [key, rawValue] = splitYamlKeyValue(itemText);\n const objectValue: Record<string, unknown> = {};\n\n if (rawValue === undefined) {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= indent) {\n throw new Error(`Expected nested value for \"${key}\" in tools.yaml`);\n }\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n objectValue[key] = nestedValue;\n index = nextIndex;\n } else {\n objectValue[key] = normalizeScalar(rawValue);\n index += 1;\n }\n\n while (index < lines.length && lines[index].indent > indent) {\n const nestedLine = lines[index];\n if (nestedLine.indent !== indent + 2 || nestedLine.text.startsWith('-')) {\n const [nestedValue, nextIndex] = parseYamlValue(lines, index, nestedLine.indent);\n if (!isPlainObject(nestedValue)) {\n throw new Error(`Expected object entry in tools.yaml at line ${index + 1}`);\n }\n Object.assign(objectValue, nestedValue);\n index = nextIndex;\n continue;\n }\n\n const [nestedKey, nestedRawValue] = splitYamlKeyValue(nestedLine.text);\n if (nestedRawValue === undefined) {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= nestedLine.indent) {\n throw new Error(`Expected nested value for \"${nestedKey}\" in tools.yaml`);\n }\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n objectValue[nestedKey] = nestedValue;\n index = nextIndex;\n continue;\n }\n\n objectValue[nestedKey] = normalizeScalar(nestedRawValue);\n index += 1;\n }\n\n result.push(objectValue);\n continue;\n }\n\n result.push(normalizeScalar(itemText));\n index += 1;\n }\n\n return [result, index];\n}\n\nfunction parseYamlObject(lines: YamlLine[], startIndex: number, indent: number): [Record<string, unknown>, number] {\n const result: Record<string, unknown> = {};\n let index = startIndex;\n\n while (index < lines.length) {\n const line = lines[index];\n if (line.indent < indent) {\n break;\n }\n if (line.indent !== indent || line.text.startsWith('-')) {\n break;\n }\n\n const [key, rawValue] = splitYamlKeyValue(line.text);\n\n if (rawValue === undefined) {\n const nextLine = lines[index + 1];\n if (!nextLine || nextLine.indent <= indent) {\n result[key] = null;\n index += 1;\n continue;\n }\n\n const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n result[key] = nestedValue;\n index = nextIndex;\n continue;\n }\n\n result[key] = normalizeScalar(rawValue);\n index += 1;\n }\n\n return [result, index];\n}\n\nfunction splitYamlKeyValue(input: string): [string, string | undefined] {\n const separatorIndex = input.indexOf(':');\n if (separatorIndex === -1) {\n throw new Error(`Invalid tools.yaml entry: \"${input}\"`);\n }\n\n const key = input.slice(0, separatorIndex).trim();\n const rawValue = input.slice(separatorIndex + 1).trim();\n return [key, rawValue === '' ? undefined : rawValue];\n}\n\nfunction parseYamlDocument(input: string): unknown {\n const lines = toYamlLines(input);\n if (lines.length === 0) {\n return {};\n }\n const [value] = parseYamlValue(lines, 0, lines[0].indent);\n return value;\n}\n\nfunction ensureExecutionTargetSchema(tool: CustomToolManifestEntry): void {\n const properties = tool.inputSchema.properties;\n\n if (!properties || !isPlainObject(properties)) {\n throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties`);\n }\n\n const pageIdDefinition = properties[REQUIRED_PAGE_ID_FIELD];\n const browserIdDefinition = properties[OPTIONAL_BROWSER_ID_FIELD];\n const hasPageId =\n pageIdDefinition !== undefined && isPlainObject(pageIdDefinition) && pageIdDefinition.type === 'string';\n const hasBrowserId =\n browserIdDefinition !== undefined && isPlainObject(browserIdDefinition) && browserIdDefinition.type === 'string';\n\n if (!hasPageId && !hasBrowserId) {\n throw new Error(\n `Custom tool \"${tool.name}\" must define inputSchema.properties.pageId and/or inputSchema.properties.browserId as type \"string\"`,\n );\n }\n\n if (pageIdDefinition !== undefined && !hasPageId) {\n throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties.pageId as type \"string\"`);\n }\n\n if (browserIdDefinition !== undefined && !hasBrowserId) {\n throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties.browserId as type \"string\"`);\n }\n}\n\nasync function loadCustomToolModule(scriptPath: string): Promise<LoadedCustomToolModule> {\n const source = await readFile(scriptPath, 'utf8');\n const compiledSource = stripTypeScriptTypes(source, { mode: 'strip' });\n const moduleUrl = `data:text/javascript;base64,${Buffer.from(compiledSource, 'utf8').toString('base64')}`;\n return (await import(moduleUrl)) as LoadedCustomToolModule;\n}\n\nfunction validateCustomToolModule(\n scriptPath: string,\n loadedModule: LoadedCustomToolModule,\n): {\n execute?: CustomToolHandler;\n} {\n const execute = typeof loadedModule.run === 'function' ? (loadedModule.run as CustomToolHandler) : undefined;\n\n if (!execute) {\n throw new Error(`Custom tool script \"${scriptPath}\" must export a \"run\" function`);\n }\n\n return { execute };\n}\n\nfunction injectSuggestionActionsIntoObject(\n value: Record<string, unknown>,\n suggestionActions: string,\n): Record<string, unknown> {\n if (typeof value.suggestionActions === 'string' && value.suggestionActions.trim().length > 0) {\n return value;\n }\n\n return {\n ...value,\n suggestionActions,\n };\n}\n\nfunction injectSuggestionActionsIntoCallToolResult(result: CallToolResult, suggestionActions: string): CallToolResult {\n const firstContent = result.content[0];\n if (firstContent?.type === 'text' && typeof firstContent.text === 'string') {\n try {\n const parsed = JSON.parse(firstContent.text);\n if (isPlainObject(parsed)) {\n const merged = injectSuggestionActionsIntoObject(parsed, suggestionActions);\n return {\n ...result,\n content: [{ ...firstContent, text: JSON.stringify(merged, null, 2) }, ...result.content.slice(1)],\n };\n }\n } catch {\n // Keep the original text payload when it is not valid JSON.\n }\n }\n\n return {\n ...result,\n content: [\n ...result.content,\n {\n type: 'text',\n text: `suggestionActions: ${suggestionActions}`,\n },\n ],\n };\n}\n\nfunction normalizeExecutionResult(toolName: string, value: unknown, suggestionActions?: string): CallToolResult {\n if (isCallToolResult(value)) {\n return suggestionActions ? injectSuggestionActionsIntoCallToolResult(value, suggestionActions) : value;\n }\n\n if (typeof value === 'string') {\n if (!suggestionActions) {\n return { content: [{ type: 'text', text: value }] };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ result: value, suggestionActions }, null, 2),\n },\n ],\n };\n }\n\n if (value === undefined) {\n if (!suggestionActions) {\n return { content: [{ type: 'text', text: `Custom tool \"${toolName}\" completed successfully` }] };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n result: `Custom tool \"${toolName}\" completed successfully`,\n suggestionActions,\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n const normalizedValue =\n isPlainObject(value) && suggestionActions ? injectSuggestionActionsIntoObject(value, suggestionActions) : value;\n return {\n content: [{ type: 'text', text: JSON.stringify(normalizedValue, null, 2) }],\n };\n}\n\nexport class CustomToolService {\n constructor(\n private readonly pageRegistry: IPageRegistry,\n private readonly extensionTaskQueue?: ExtensionTaskQueue,\n private readonly telemetry: ITelemetryService = new TelemetryService(),\n private readonly browserService?: IBrowserService,\n ) {}\n\n private resolveToolPage(toolName: string, pageId: string, pageEntry: PageEntry): CustomToolPage {\n if (pageEntry.page) {\n return pageEntry.page;\n }\n\n if (pageEntry.mode === 'extension' && this.extensionTaskQueue) {\n const proxy = new ExtensionPageProxy(this.extensionTaskQueue);\n proxy.setTarget(pageId, pageEntry.browserId);\n return proxy;\n }\n\n throw new Error(`Custom tool \"${toolName}\" requires a supported page context`);\n }\n\n private toPageSummary(currentPageId: string | null, entry: PageEntry): CustomToolBrowserPageSummary {\n return {\n pageId: entry.id,\n url: entry.url,\n title: entry.title,\n active: currentPageId === entry.id,\n };\n }\n\n private async createPageForBrowser(\n toolName: string,\n browserId: string,\n options?: { url?: string; setAsCurrent?: boolean },\n ): Promise<CustomToolBrowserPageHandle> {\n if (!this.browserService) {\n throw new Error(`Custom tool \"${toolName}\" requires browser service support to create a page`);\n }\n\n const browserInstance = this.browserService.getBrowser(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n const setAsCurrent = options?.setAsCurrent !== false;\n\n if (browserInstance.mode === 'extension' || browserInstance.mode === 'vm') {\n if (!this.extensionTaskQueue) {\n throw new Error(`Custom tool \"${toolName}\" requires extension task support to create a page`);\n }\n\n const pageId = this.pageRegistry.registerExtensionPage(browserId, undefined, options?.url, false);\n\n try {\n const queued = await this.extensionTaskQueue.queueTask(\n 'browser_new_page',\n {\n browserId,\n pageId,\n url: options?.url,\n setAsCurrent,\n },\n 10_000,\n browserId,\n );\n\n if (!queued.success) {\n throw new Error(queued.error ?? `Custom tool \"${toolName}\" failed to create a page`);\n }\n\n const delegatedText = queued.result?.content[0]?.type === 'text' ? queued.result.content[0].text : undefined;\n let delegatedPayload: { url?: string; title?: string; tabId?: number } = {};\n if (typeof delegatedText === 'string' && delegatedText.length > 0) {\n try {\n delegatedPayload = JSON.parse(delegatedText) as { url?: string; title?: string; tabId?: number };\n } catch {\n delegatedPayload = {};\n }\n }\n\n const pageEntry = this.pageRegistry.get(pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageId}\" was not registered`);\n }\n\n pageEntry.url = delegatedPayload.url ?? pageEntry.url;\n pageEntry.title = delegatedPayload.title ?? pageEntry.title;\n pageEntry.extensionTabId = delegatedPayload.tabId ?? pageEntry.extensionTabId;\n const resolvedPageEntry = await this.waitForResolvedPageMetadata(pageId);\n browserInstance.pageIds.add(pageId);\n if (setAsCurrent || !browserInstance.currentPageId) {\n this.browserService.setCurrentPage(browserId, pageId);\n }\n this.browserService.recordBrowserActivity(browserId, pageId);\n\n return {\n ...this.toPageSummary(browserInstance.currentPageId, resolvedPageEntry),\n page: this.resolveToolPage(toolName, pageId, resolvedPageEntry),\n };\n } catch (error) {\n this.pageRegistry.remove(pageId);\n throw error;\n }\n }\n\n const { pageId, page } = await this.browserService.newPage(browserId);\n if (options?.url) {\n await page.goto(options.url);\n await this.pageRegistry.updateMetadata(pageId);\n }\n\n if (setAsCurrent) {\n this.browserService.setCurrentPage(browserId, pageId);\n }\n this.browserService.recordBrowserActivity(browserId, pageId);\n\n const pageEntry = this.pageRegistry.get(pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageId}\" was not registered`);\n }\n\n return {\n ...this.toPageSummary(browserInstance.currentPageId, pageEntry),\n page: this.resolveToolPage(toolName, pageId, pageEntry),\n };\n }\n\n private async waitForResolvedPageMetadata(pageId: string): Promise<PageEntry> {\n const startedAt = Date.now();\n let entry = this.pageRegistry.get(pageId);\n\n while (entry && Date.now() - startedAt < CUSTOM_TOOL_PAGE_METADATA_WAIT_TIMEOUT_MS) {\n const hasUrl = typeof entry.url === 'string' && entry.url.length > 0;\n const hasTitle = typeof entry.title === 'string' && entry.title.length > 0 && entry.title !== 'Extension Tab';\n if (hasUrl && hasTitle) {\n return entry;\n }\n\n await new Promise((resolve) => setTimeout(resolve, CUSTOM_TOOL_PAGE_METADATA_WAIT_POLL_MS));\n entry = this.pageRegistry.get(pageId);\n }\n\n if (!entry) {\n throw new Error(`Page \"${pageId}\" was not registered`);\n }\n\n return entry;\n }\n\n private async resolveExecutionContext(\n toolName: string,\n input: Record<string, unknown>,\n ): Promise<{ pageId: string; pageEntry: PageEntry; page: CustomToolPage; browser: CustomToolBrowser }> {\n const requestedPageId =\n typeof input[REQUIRED_PAGE_ID_FIELD] === 'string' && input[REQUIRED_PAGE_ID_FIELD].length > 0\n ? input[REQUIRED_PAGE_ID_FIELD]\n : undefined;\n const requestedBrowserId =\n typeof input[OPTIONAL_BROWSER_ID_FIELD] === 'string' && input[OPTIONAL_BROWSER_ID_FIELD].length > 0\n ? input[OPTIONAL_BROWSER_ID_FIELD]\n : undefined;\n\n if (!requestedPageId && !requestedBrowserId) {\n throw new Error(`Custom tool \"${toolName}\" requires a string pageId or browserId`);\n }\n\n if (requestedPageId) {\n const pageEntry = this.pageRegistry.get(requestedPageId);\n if (!pageEntry) {\n throw new Error(`Page \"${requestedPageId}\" not found`);\n }\n if (requestedBrowserId && pageEntry.browserId !== requestedBrowserId) {\n throw new Error(\n `Custom tool \"${toolName}\" received pageId \"${requestedPageId}\" for browser \"${pageEntry.browserId}\", not \"${requestedBrowserId}\"`,\n );\n }\n\n const browser = this.createBrowserHelper(toolName, pageEntry.browserId);\n return {\n pageId: requestedPageId,\n pageEntry,\n page: this.resolveToolPage(toolName, requestedPageId, pageEntry),\n browser,\n };\n }\n\n const browser = this.createBrowserHelper(toolName, requestedBrowserId as string);\n try {\n const pageHandle = await browser.getCurrentPage();\n const pageEntry = this.pageRegistry.get(pageHandle.pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageHandle.pageId}\" not found`);\n }\n\n return {\n pageId: pageHandle.pageId,\n pageEntry,\n page: pageHandle.page,\n browser,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (!message.includes('has no pages')) {\n throw error;\n }\n\n const pageHandle = await browser.newPage();\n const pageEntry = this.pageRegistry.get(pageHandle.pageId);\n if (!pageEntry) {\n throw new Error(`Page \"${pageHandle.pageId}\" not found`, {\n cause: error,\n });\n }\n\n return {\n pageId: pageHandle.pageId,\n pageEntry,\n page: pageHandle.page,\n browser,\n };\n }\n }\n\n private createBrowserHelper(toolName: string, browserId: string): CustomToolBrowser {\n const listPages = async (): Promise<CustomToolBrowserPageSummary[]> => {\n const browserInstance = this.browserService?.getBrowser(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n return this.pageRegistry\n .findByBrowser(browserId)\n .map((entry) => this.toPageSummary(browserInstance.currentPageId, entry));\n };\n\n return {\n browserId,\n mode: this.browserService?.getBrowser(browserId)?.mode ?? 'extension',\n listPages,\n getPage: async (pageId: string): Promise<CustomToolBrowserPageHandle> => {\n const pageEntry = this.pageRegistry.get(pageId);\n if (!pageEntry || pageEntry.browserId !== browserId) {\n throw new Error(`Page \"${pageId}\" not found in browser \"${browserId}\"`);\n }\n\n const browserInstance = this.browserService?.getBrowser(browserId);\n return {\n ...this.toPageSummary(browserInstance?.currentPageId ?? null, pageEntry),\n page: this.resolveToolPage(toolName, pageId, pageEntry),\n };\n },\n getCurrentPage: async (): Promise<CustomToolBrowserPageHandle> => {\n const browserInstance = this.browserService?.getBrowser(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n const currentPageId =\n browserInstance.currentPageId ?? this.pageRegistry.findByBrowser(browserId)[0]?.id ?? undefined;\n if (!currentPageId) {\n throw new Error(`Browser \"${browserId}\" has no pages`);\n }\n\n const pageEntry = this.pageRegistry.get(currentPageId);\n if (!pageEntry) {\n throw new Error(`Page \"${currentPageId}\" not found`);\n }\n\n return {\n ...this.toPageSummary(browserInstance.currentPageId, pageEntry),\n page: this.resolveToolPage(toolName, currentPageId, pageEntry),\n };\n },\n newPage: async (options?: { url?: string; setAsCurrent?: boolean }): Promise<CustomToolBrowserPageHandle> =>\n this.createPageForBrowser(toolName, browserId, options),\n };\n }\n\n private createToolLogger(toolName: string, pageId: string, pageEntry: PageEntry): CustomToolLogger {\n const baseAttributes: Record<string, unknown> = {\n 'browse_tool.tool.name': toolName,\n 'browse_tool.page.id': pageId,\n 'browse_tool.browser.id': pageEntry.browserId,\n 'browse_tool.execution.mode': pageEntry.mode,\n };\n\n const emit = (\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal',\n message: string,\n options?: CustomToolLogOptions,\n ): void => {\n this.telemetry.log(level, message, {\n attributes: toAttributes({\n ...baseAttributes,\n ...(options?.attributes ?? {}),\n }),\n exception: options?.exception,\n });\n };\n\n return {\n getTraceContext: () => this.telemetry.getActiveTraceContext(),\n trace: (message, options) => emit('trace', message, options),\n debug: (message, options) => emit('debug', message, options),\n info: (message, options) => emit('info', message, options),\n warn: (message, options) => emit('warn', message, options),\n error: (message, options) => emit('error', message, options),\n fatal: (message, options) => emit('fatal', message, options),\n };\n }\n\n async listTools(directory: string): Promise<CustomToolDefinition[]> {\n const tools = await this.loadTools(directory);\n return tools.map(({ name, description, suggestionActions, inputSchema, capabilities }) => ({\n name,\n description,\n suggestionActions,\n inputSchema,\n capabilities,\n }));\n }\n\n async executeTool(directory: string, toolName: string, input: Record<string, unknown>): Promise<CallToolResult> {\n const requestedPageId =\n typeof input[REQUIRED_PAGE_ID_FIELD] === 'string' ? input[REQUIRED_PAGE_ID_FIELD] : undefined;\n const requestedBrowserId =\n typeof input[OPTIONAL_BROWSER_ID_FIELD] === 'string' ? input[OPTIONAL_BROWSER_ID_FIELD] : undefined;\n\n return this.telemetry.runInSpan(\n 'browse_tool.custom_tool.execute',\n {\n attributes: {\n 'browse_tool.tool.name': toolName,\n 'browse_tool.custom_tools.directory': path.resolve(directory),\n 'browse_tool.page.id': requestedPageId,\n 'browse_tool.browser.id': requestedBrowserId,\n },\n },\n async (span) => {\n const tools = await this.loadTools(directory);\n const tool = tools.find((candidate) => candidate.name === toolName);\n\n if (!tool) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: `Custom tool \"${toolName}\" not found` });\n throw new Error(`Custom tool \"${toolName}\" not found`);\n }\n\n const { pageId, pageEntry, page, browser } = await this.resolveExecutionContext(toolName, input);\n const logger = this.createToolLogger(toolName, pageId, pageEntry);\n span?.setAttributes({\n 'browse_tool.browser.id': pageEntry.browserId,\n 'browse_tool.execution.mode': pageEntry.mode,\n 'browse_tool.page.id': pageId,\n });\n debugCustomTool('Executing custom tool', {\n toolName,\n directory: path.resolve(directory),\n pageId,\n browserId: pageEntry.browserId,\n mode: pageEntry.mode,\n input: summarizeValue(input),\n scriptPath: tool.scriptPath,\n });\n\n let rawResult;\n try {\n rawResult = await tool.execute?.({ page, browser, input, logger });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n debugCustomTool('Custom tool execution failed', {\n toolName,\n pageId,\n browserId: pageEntry.browserId,\n mode: pageEntry.mode,\n error: message,\n stack: error instanceof Error ? error.stack : undefined,\n });\n throw new Error(`Custom tool \"${toolName}\" failed on page \"${pageId}\": ${message}`, {\n cause: error,\n });\n }\n\n debugCustomTool('Custom tool execution completed', {\n toolName,\n pageId,\n browserId: pageEntry.browserId,\n mode: pageEntry.mode,\n result: summarizeValue(rawResult),\n });\n\n const result = normalizeExecutionResult(toolName, rawResult, tool.suggestionActions);\n const errorMessage = result.isError ? (result.content[0] as { text?: string })?.text : undefined;\n if (errorMessage) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n }\n\n return result;\n },\n );\n }\n\n private async loadTools(directory: string): Promise<LoadedCustomTool[]> {\n const resolvedDirectory = path.resolve(directory);\n const manifestPath = path.join(resolvedDirectory, MANIFEST_FILE);\n const manifestSource = await readFile(manifestPath, 'utf8').catch((error) => {\n throw new Error(\n `Failed to read custom tool manifest at \"${manifestPath}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n });\n\n const parsedManifest = parseYamlDocument(manifestSource);\n const manifest = CustomToolManifestSchema.parse(parsedManifest);\n\n return Promise.all(\n manifest.tools.map(async (tool) => {\n ensureExecutionTargetSchema(tool);\n\n const scriptPath = path.resolve(resolvedDirectory, tool.script);\n await stat(scriptPath).catch((error) => {\n throw new Error(\n `Custom tool \"${tool.name}\" script not found at \"${scriptPath}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n });\n\n const loadedModule = await loadCustomToolModule(scriptPath);\n const executionHooks = validateCustomToolModule(scriptPath, loadedModule);\n\n return {\n name: tool.name,\n description: tool.description,\n suggestionActions: tool.suggestionActions,\n inputSchema: tool.inputSchema,\n capabilities: tool.capabilities,\n scriptPath,\n ...executionHooks,\n };\n }),\n );\n }\n}\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\n\n/**\n * Browser data for API response\n */\ninterface BrowserResponse {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: string;\n pages: PageResponse[];\n}\n\n/**\n * Page data for API response\n */\ninterface PageResponse {\n id: string;\n url: string;\n title: string;\n createdAt: string;\n}\n\n/**\n * Stats for API response\n */\ninterface StatsResponse {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * Create API router for dashboard\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with API endpoints\n */\nexport function createApiRouter(container: Container): Hono {\n const app = new Hono();\n\n /**\n * GET /api/browsers - List all browsers with nested pages and stats\n */\n app.get('/browsers', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const browserInstances = browserService.listBrowsers();\n const allPages = pageRegistry.list();\n\n // Build browser response with nested pages\n const browsers: BrowserResponse[] = browserInstances.map((browser: BrowserInstance) => {\n const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n return {\n id: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n createdAt: browser.createdAt.toISOString(),\n pages: browserPages.map((page: PageEntry) => ({\n id: page.id,\n url: page.url,\n title: page.title,\n createdAt: page.createdAt.toISOString(),\n })),\n };\n });\n\n const stats: StatsResponse = {\n totalBrowsers: browserInstances.length,\n totalPages: allPages.length,\n };\n\n return c.json({ browsers, stats });\n } catch (error) {\n console.error('Failed to list browsers:', error);\n return c.json(\n {\n error: 'Failed to list browsers',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/browsers - Close all browsers\n */\n app.delete('/browsers', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n await browserService.closeAll();\n\n return c.json({ success: true, message: 'All browsers closed' });\n } catch (error) {\n console.error('Failed to close all browsers:', error);\n return c.json(\n {\n error: 'Failed to close all browsers',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/browsers/:id - Close a specific browser\n */\n app.delete('/browsers/:id', async (c) => {\n try {\n const browserId = c.req.param('id');\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n\n const browser = browserService.getBrowser(browserId);\n if (!browser) {\n return c.json({ error: `Browser \"${browserId}\" not found` }, 404);\n }\n\n await browserService.closeBrowser(browserId);\n\n return c.json({ success: true, message: `Browser \"${browserId}\" closed` });\n } catch (error) {\n console.error('Failed to close browser:', error);\n return c.json(\n {\n error: 'Failed to close browser',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/pages/:id - Close a specific page\n */\n app.delete('/pages/:id', async (c) => {\n try {\n const pageId = c.req.param('id');\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const pageEntry = pageRegistry.get(pageId);\n if (!pageEntry) {\n return c.json({ error: `Page \"${pageId}\" not found` }, 404);\n }\n\n if (!pageEntry.page) {\n return c.json({ error: `Page \"${pageId}\" is in extension mode and cannot be closed via API` }, 400);\n }\n\n // Close the page - this will trigger the registry cleanup via the 'close' event\n await pageEntry.page.close();\n\n return c.json({ success: true, message: `Page \"${pageId}\" closed` });\n } catch (error) {\n console.error('Failed to close page:', error);\n return c.json(\n {\n error: 'Failed to close page',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n return app;\n}\n","/**\n * Utility formatters for dashboard display\n */\n\n/**\n * Format a Date object to a human-readable string\n *\n * @param date - Date to format\n * @returns Formatted timestamp string\n */\nexport function formatTimestamp(date: Date): string {\n return date.toLocaleString('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n}\n\n/**\n * Format duration in milliseconds to human readable\n *\n * @param ms - Duration in milliseconds\n * @returns Formatted duration string\n */\nexport function formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Calculate duration since a given date\n *\n * @param date - Start date\n * @returns Duration string\n */\nexport function formatAge(date: Date): string {\n const now = new Date();\n const ms = now.getTime() - date.getTime();\n return formatDuration(ms);\n}\n","// Plain CSS class names for SSR compatibility\nexport const container = 'dashboard-container';\nexport const header = 'dashboard-header';\nexport const controlsSection = 'controls-section';\nexport const button = 'btn';\nexport const buttonDanger = 'btn-danger';\nexport const tableContainer = 'table-container';\nexport const table = 'browser-table';\nexport const statsContainer = 'stats-container';\nexport const statCard = 'stat-card';\nexport const browserRow = 'browser-row';\nexport const pageRow = 'page-row';\nexport const statusActive = 'status-active';\nexport const statusInactive = 'status-inactive';\nexport const urlCell = 'url-cell';\nexport const timestampCell = 'timestamp-cell';\nexport const actionsCell = 'actions-cell';\nexport const killButton = 'kill-btn';\nexport const refreshButton = 'refresh-btn';\nexport const emptyState = 'empty-state';\n\n// Global styles as plain CSS string for SSR\nexport const globalStyles = `\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #333;\n background-color: #f5f5f5;\n }\n\n h1, h2, h3 {\n margin-bottom: 1rem;\n }\n\n .dashboard-container {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n }\n\n .dashboard-header {\n background-color: #fff;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .dashboard-header h1 {\n margin-bottom: 0;\n }\n\n .controls-section {\n background-color: #fff;\n padding: 1rem;\n margin-bottom: 1rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n display: flex;\n gap: 1rem;\n align-items: center;\n flex-wrap: wrap;\n }\n\n .btn {\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n font-size: 14px;\n cursor: pointer;\n background-color: #2196f3;\n color: white;\n transition: background-color 0.2s;\n }\n\n .btn:hover {\n background-color: #1976d2;\n }\n\n .btn:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n }\n\n .btn-danger {\n background-color: #f44336;\n }\n\n .btn-danger:hover {\n background-color: #d32f2f;\n }\n\n .table-container {\n background-color: #fff;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n }\n\n .browser-table {\n width: 100%;\n border-collapse: collapse;\n table-layout: fixed;\n }\n\n .browser-table th {\n background-color: #f8f9fa;\n padding: 0.75rem;\n text-align: left;\n font-weight: 600;\n border-bottom: 2px solid #dee2e6;\n position: sticky;\n top: 0;\n }\n\n .browser-table th:nth-child(1) {\n width: 12%;\n }\n\n .browser-table th:nth-child(2) {\n width: 10%;\n }\n\n .browser-table th:nth-child(3) {\n width: 38%;\n }\n\n .browser-table th:nth-child(4) {\n width: 15%;\n }\n\n .browser-table th:nth-child(5) {\n width: 15%;\n }\n\n .browser-table th:nth-child(6) {\n width: 10%;\n }\n\n .browser-table td {\n padding: 0.75rem;\n border-bottom: 1px solid #dee2e6;\n vertical-align: middle;\n }\n\n .browser-row {\n background-color: #e3f2fd;\n font-weight: 500;\n }\n\n .browser-row:hover {\n background-color: #bbdefb;\n }\n\n .page-row {\n background-color: #fff;\n }\n\n .page-row:hover {\n background-color: #f8f9fa;\n }\n\n .page-row td:first-child {\n padding-left: 2rem;\n }\n\n .status-active {\n color: #4caf50;\n font-weight: 600;\n }\n\n .status-inactive {\n color: #9e9e9e;\n }\n\n .url-cell {\n max-width: 400px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: 'Courier New', monospace;\n font-size: 13px;\n }\n\n .timestamp-cell {\n white-space: nowrap;\n font-size: 12px;\n color: #666;\n }\n\n .actions-cell {\n text-align: center;\n }\n\n .kill-btn {\n padding: 0.25rem 0.5rem;\n font-size: 12px;\n background-color: #ff5722;\n border: none;\n border-radius: 4px;\n color: white;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n .kill-btn:hover {\n background-color: #e64a19;\n }\n\n .refresh-btn {\n background-color: #4caf50;\n }\n\n .refresh-btn:hover {\n background-color: #388e3c;\n }\n\n .stats-container {\n display: flex;\n gap: 1.5rem;\n margin-bottom: 1rem;\n }\n\n .stat-card {\n background-color: #fff;\n padding: 1rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n flex: 1;\n }\n\n .stat-card h3 {\n font-size: 0.875rem;\n color: #666;\n margin-bottom: 0.5rem;\n }\n\n .stat-card .value {\n font-size: 1.5rem;\n font-weight: 700;\n color: #333;\n }\n\n .empty-state {\n padding: 3rem;\n text-align: center;\n color: #666;\n }\n\n .empty-state h3 {\n margin-bottom: 0.5rem;\n color: #999;\n }\n`;\n","import { formatAge, formatTimestamp } from '../utils/formatters.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * BrowserTable component props\n */\ninterface BrowserTableProps {\n browsers: BrowserData[];\n}\n\n/**\n * BrowserTable component\n *\n * Displays browsers and their pages in a hierarchical table\n */\nexport function BrowserTable({ browsers }: BrowserTableProps) {\n if (browsers.length === 0) {\n return (\n <div class={styles.tableContainer}>\n <div class={styles.emptyState}>\n <h3>No Active Browsers</h3>\n <p>Launch a browser using MCP tools to see it here.</p>\n </div>\n </div>\n );\n }\n\n return (\n <div class={styles.tableContainer}>\n <table class={styles.table}>\n <thead>\n <tr>\n <th>ID</th>\n <th>Status</th>\n <th>URL / Title</th>\n <th>Created</th>\n <th>Age</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody id=\"browser-table-body\">\n {browsers.map((browser) => (\n <>\n <tr class={styles.browserRow}>\n <td>{browser.id}</td>\n <td>\n <span class={styles.statusActive}>\n {browser.pages.length} page{browser.pages.length !== 1 ? 's' : ''}\n </span>\n </td>\n <td>{browser.profileName || 'Default Profile'}</td>\n <td class={styles.timestampCell}>{formatTimestamp(browser.createdAt)}</td>\n <td class={styles.timestampCell}>{formatAge(browser.createdAt)}</td>\n <td class={styles.actionsCell}>\n <button class={styles.killButton} onclick={`dashboard.killBrowser('${browser.id}')`}>\n Kill\n </button>\n </td>\n </tr>\n {browser.pages.map((page) => (\n <tr class={styles.pageRow}>\n <td>\n {page.id}\n {browser.currentPageId === page.id && ' (active)'}\n </td>\n <td>\n <span class={styles.statusActive}>Open</span>\n </td>\n <td class={styles.urlCell} title={page.url}>\n {page.title || page.url || 'about:blank'}\n </td>\n <td class={styles.timestampCell}>{formatTimestamp(page.createdAt)}</td>\n <td class={styles.timestampCell}>{formatAge(page.createdAt)}</td>\n <td class={styles.actionsCell}>\n <button class={styles.killButton} onclick={`dashboard.killPage('${page.id}')`}>\n Close\n </button>\n </td>\n </tr>\n ))}\n </>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n","import * as styles from './styles.js';\n\n/**\n * Stats data structure\n */\ninterface Stats {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * StatsHeader component props\n */\ninterface StatsHeaderProps {\n stats: Stats;\n}\n\n/**\n * StatsHeader component\n *\n * Displays browser and page statistics\n */\nexport function StatsHeader({ stats }: StatsHeaderProps) {\n return (\n <div class={styles.statsContainer}>\n <div class={styles.statCard}>\n <h3>Active Browsers</h3>\n <div class=\"value\">{stats.totalBrowsers}</div>\n </div>\n <div class={styles.statCard}>\n <h3>Active Pages</h3>\n <div class=\"value\">{stats.totalPages}</div>\n </div>\n </div>\n );\n}\n","import { raw } from 'hono/html';\nimport { BrowserTable } from './BrowserTable.js';\nimport { StatsHeader } from './StatsHeader.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * Stats data structure\n */\ninterface Stats {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * Dashboard component props\n */\ninterface DashboardProps {\n browsers: BrowserData[];\n stats: Stats;\n}\n\n/**\n * Dashboard component\n *\n * Main dashboard UI with auto-refresh polling and browser/page management\n */\nexport function Dashboard({ browsers, stats }: DashboardProps) {\n return (\n <div class={styles.container}>\n <div class={styles.header}>\n <div>\n <h1>Playwright MCP Dashboard</h1>\n <p>Browser automation management (auto-refresh every 3 seconds)</p>\n </div>\n <div>\n <button\n id=\"refresh-btn\"\n class={`${styles.button} ${styles.refreshButton}`}\n onclick=\"dashboard.manualRefresh()\"\n >\n Refresh Now\n </button>\n <button\n id=\"kill-all-btn\"\n class={`${styles.button} ${styles.buttonDanger}`}\n onclick=\"dashboard.killAllBrowsers()\"\n >\n Kill All Browsers\n </button>\n </div>\n </div>\n\n <StatsHeader stats={stats} />\n\n <BrowserTable browsers={browsers} />\n\n <script>\n {raw(`\nclass DashboardManager {\n constructor() {\n this.autoRefreshInterval = null;\n this.isRefreshing = false;\n this.startAutoRefresh();\n }\n\n startAutoRefresh() {\n this.autoRefreshInterval = setInterval(() => {\n this.fetchBrowsers();\n }, 3000);\n }\n\n stopAutoRefresh() {\n if (this.autoRefreshInterval) {\n clearInterval(this.autoRefreshInterval);\n this.autoRefreshInterval = null;\n }\n }\n\n async fetchBrowsers() {\n if (this.isRefreshing) return;\n this.isRefreshing = true;\n\n try {\n const response = await fetch('/api/browsers');\n const data = await response.json();\n this.updateStats(data.stats);\n this.updateBrowserTable(data.browsers);\n } catch (error) {\n console.error('Failed to fetch browsers:', error);\n } finally {\n this.isRefreshing = false;\n }\n }\n\n updateStats(stats) {\n const cards = document.querySelectorAll('.${styles.statCard} .value');\n if (cards.length >= 2) {\n cards[0].textContent = stats.totalBrowsers;\n cards[1].textContent = stats.totalPages;\n }\n }\n\n updateBrowserTable(browsers) {\n const tbody = document.getElementById('browser-table-body');\n if (!tbody) return;\n\n if (browsers.length === 0) {\n const container = tbody.closest('.${styles.tableContainer}');\n if (container) {\n container.innerHTML = \\`\n <div class=\"${styles.emptyState}\">\n <h3>No Active Browsers</h3>\n <p>Launch a browser using MCP tools to see it here.</p>\n </div>\n \\`;\n }\n return;\n }\n\n // Rebuild table content\n tbody.innerHTML = '';\n browsers.forEach(browser => {\n // Browser row\n const browserRow = document.createElement('tr');\n browserRow.className = '${styles.browserRow}';\n browserRow.innerHTML = \\`\n <td>\\${browser.id}</td>\n <td><span class=\"${styles.statusActive}\">\\${browser.pages.length} page\\${browser.pages.length !== 1 ? 's' : ''}</span></td>\n <td>\\${browser.profileName || 'Default Profile'}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatTimestamp(browser.createdAt)}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatAge(browser.createdAt)}</td>\n <td class=\"${styles.actionsCell}\"><button class=\"${styles.killButton}\" onclick=\"dashboard.killBrowser('\\${browser.id}')\">Kill</button></td>\n \\`;\n tbody.appendChild(browserRow);\n\n // Page rows\n browser.pages.forEach(page => {\n const pageRow = document.createElement('tr');\n pageRow.className = '${styles.pageRow}';\n const isActive = browser.currentPageId === page.id;\n pageRow.innerHTML = \\`\n <td>\\${page.id}\\${isActive ? ' (active)' : ''}</td>\n <td><span class=\"${styles.statusActive}\">Open</span></td>\n <td class=\"${styles.urlCell}\" title=\"\\${this.escapeHtml(page.url)}\">\\${this.escapeHtml(page.title || page.url || 'about:blank')}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatTimestamp(page.createdAt)}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatAge(page.createdAt)}</td>\n <td class=\"${styles.actionsCell}\"><button class=\"${styles.killButton}\" onclick=\"dashboard.killPage('\\${page.id}')\">Close</button></td>\n \\`;\n tbody.appendChild(pageRow);\n });\n });\n }\n\n formatTimestamp(timestamp) {\n const date = new Date(timestamp);\n return date.toLocaleString('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n }\n\n formatAge(timestamp) {\n const now = new Date();\n const date = new Date(timestamp);\n const ms = now.getTime() - date.getTime();\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) return \\`\\${hours}h \\${minutes % 60}m\\`;\n if (minutes > 0) return \\`\\${minutes}m \\${seconds % 60}s\\`;\n return \\`\\${seconds}s\\`;\n }\n\n escapeHtml(str) {\n if (typeof str !== 'string') return str;\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n manualRefresh() {\n this.fetchBrowsers();\n }\n\n async killBrowser(browserId) {\n try {\n const response = await fetch(\\`/api/browsers/\\${browserId}\\`, { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to kill browser:', await response.text());\n }\n } catch (error) {\n console.error('Failed to kill browser:', error);\n }\n }\n\n async killPage(pageId) {\n try {\n const response = await fetch(\\`/api/pages/\\${pageId}\\`, { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to close page:', await response.text());\n }\n } catch (error) {\n console.error('Failed to close page:', error);\n }\n }\n\n async killAllBrowsers() {\n if (!confirm('Are you sure you want to close all browsers?')) return;\n\n try {\n const response = await fetch('/api/browsers', { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to kill all browsers:', await response.text());\n }\n } catch (error) {\n console.error('Failed to kill all browsers:', error);\n }\n }\n}\n\n// Initialize dashboard\nconst dashboard = new DashboardManager();\n\n// Cleanup on page unload\nwindow.addEventListener('beforeunload', () => {\n dashboard.stopAutoRefresh();\n});\n `)}\n </script>\n </div>\n );\n}\n","import { raw } from 'hono/html';\nimport { globalStyles } from './styles.js';\n\n/**\n * Layout component props\n */\ninterface LayoutProps {\n title: string;\n children: unknown;\n}\n\n/**\n * Layout component\n *\n * Provides the HTML wrapper with styles and meta tags for SSR\n */\nexport function Layout({ title, children }: LayoutProps) {\n return (\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{title}</title>\n <style>{raw(globalStyles)}</style>\n </head>\n <body>{children}</body>\n </html>\n );\n}\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\nimport { Dashboard } from '../components/Dashboard.js';\nimport { Layout } from '../components/Layout.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * Create dashboard router for server-side rendered UI\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with dashboard routes\n */\nexport function createDashboardRouter(container: Container): Hono {\n const app = new Hono();\n\n /**\n * GET / - Dashboard home page with SSR\n *\n * Renders the dashboard UI with initial server-side data\n */\n app.get('/', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const browserInstances = browserService.listBrowsers();\n const allPages = pageRegistry.list();\n\n // Build browser data with nested pages\n const browsers: BrowserData[] = browserInstances.map((browser: BrowserInstance) => {\n const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n return {\n id: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n createdAt: browser.createdAt,\n pages: browserPages.map((page: PageEntry) => ({\n id: page.id,\n url: page.url,\n title: page.title,\n createdAt: page.createdAt,\n })),\n };\n });\n\n const stats = {\n totalBrowsers: browserInstances.length,\n totalPages: allPages.length,\n };\n\n // Render dashboard with Layout wrapper\n return c.html(\n <Layout title=\"Playwright MCP Dashboard\">\n <Dashboard browsers={browsers} stats={stats} />\n </Layout>,\n );\n } catch (error) {\n console.error('Failed to render dashboard:', error);\n\n return c.html(\n <Layout title=\"Playwright MCP Dashboard - Error\">\n <div style=\"padding: 2rem; text-align: center;\">\n <h1>Failed to load dashboard</h1>\n <p>{error instanceof Error ? error.message : String(error)}</p>\n <a href=\"/\" style=\"color: #2196f3; text-decoration: underline;\">\n Retry\n </a>\n </div>\n </Layout>,\n 500,\n );\n }\n });\n\n return app;\n}\n","/**\n * HTTP Server for Browser Automation\n *\n * DESIGN PATTERNS:\n * - Hono for HTTP routing and middleware\n * - Dependency injection with InversifyJS Container\n * - Service layer for business logic\n * - Generic execute endpoint for all tools\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing and middleware\n * - Use CORS middleware for cross-origin requests\n * - Keep route handlers thin, delegate to services\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../services/BrowserService.js';\nimport { CustomToolService } from '../services/CustomToolService.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { TelemetryService, type ITelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.js';\nimport { createApiRouter } from './dashboard/routes/api.js';\nimport { createDashboardRouter } from './dashboard/routes/dashboard.js';\nimport { createExtensionRoutes } from './extension-routes.js';\n\n/**\n * Execute request body\n */\ninterface ExecuteRequest {\n tool: string;\n arguments: Record<string, unknown>;\n}\n\n/**\n * Execute response body\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: CallToolResult;\n error?: string;\n}\n\ninterface CustomToolsResponse {\n tools: Array<{\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: Record<string, unknown>;\n capabilities: Record<string, unknown>;\n }>;\n error?: string;\n}\n\n/**\n * Browser info for health response\n */\ninterface BrowserInfo {\n id: string;\n pageCount: number;\n createdAt: string;\n}\n\n/**\n * Health response\n */\ninterface HealthResponse {\n status: 'healthy' | 'unhealthy';\n service: string;\n serviceName: string;\n timestamp: string;\n browsers: {\n count: number;\n instances: BrowserInfo[];\n };\n}\n\nfunction getResultError(result: CallToolResult): string | undefined {\n if (!result.isError) {\n return undefined;\n }\n\n const firstContent = result.content[0];\n if (firstContent?.type === 'text' && typeof firstContent.text === 'string') {\n return firstContent.text;\n }\n\n return 'Unknown tool execution error';\n}\n\nfunction resolveTelemetryService(container: Container): ITelemetryService {\n try {\n return container.get<ITelemetryService>(PLAYWRIGHT_TYPES.TelemetryService);\n } catch {\n return new TelemetryService();\n }\n}\n\n/**\n * Create HTTP server for browser automation\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono app\n */\nexport function createHttpServer(container: Container): Hono {\n const app = new Hono();\n const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n PLAYWRIGHT_TYPES.PageRegistry,\n );\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const extensionTaskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const telemetry = resolveTelemetryService(container);\n const customToolService = new CustomToolService(pageRegistry, extensionTaskQueue, telemetry, browserService);\n\n // CORS middleware - allow all origins\n app.use(\n '*',\n cors({\n origin: (origin) => origin ?? null,\n credentials: true,\n allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Accept', 'Authorization'],\n }),\n );\n\n // Health check endpoint\n app.get('/health', (c) => {\n try {\n const browsers = browserService.listBrowsers();\n\n const response: HealthResponse = {\n status: 'healthy',\n service: 'browse-tool-http',\n serviceName: 'browse-tool-http',\n timestamp: new Date().toISOString(),\n browsers: {\n count: browsers.length,\n instances: browsers.map((b: BrowserInstance) => ({\n id: b.id,\n pageCount: b.pageIds.size,\n createdAt: b.createdAt.toISOString(),\n })),\n },\n };\n\n return c.json(response);\n } catch (error) {\n return c.json(\n {\n status: 'unhealthy',\n service: 'browse-tool-http',\n serviceName: 'browse-tool-http',\n error: error instanceof Error ? error.message : String(error),\n },\n 503,\n );\n }\n });\n\n // Execute tool endpoint\n app.post('/execute', async (c) => {\n try {\n const body = (await c.req.json()) as ExecuteRequest;\n\n if (!body.tool) {\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: 'Missing \"tool\" field in request body',\n },\n 400,\n );\n }\n\n const pageId = typeof body.arguments?.pageId === 'string' ? body.arguments.pageId : undefined;\n\n return await telemetry.runInSpan(\n 'browse_tool.http.execute',\n {\n attributes: {\n 'http.method': 'POST',\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n },\n },\n async (span) => {\n const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n const tool = tools.find((t) => t.getDefinition().name === body.tool);\n\n if (process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1' && body.tool === 'browser_launch') {\n const videoDir =\n typeof body.arguments?.videoDir === 'string'\n ? body.arguments.videoDir\n : typeof body.arguments?.video_dir === 'string'\n ? body.arguments.video_dir\n : '';\n console.log(\n `[ExtensionRecordingDebug] http execute browser_launch mode=${String(body.arguments?.mode ?? '')} videoDir=${videoDir}`,\n );\n }\n\n if (!tool) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: `Tool \"${body.tool}\" not found` });\n telemetry.log('warn', 'browse-tool HTTP execute rejected unknown tool', {\n attributes: {\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n },\n });\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: `Tool \"${body.tool}\" not found`,\n },\n 404,\n );\n }\n\n const result = await tool.execute(body.arguments || {});\n const args = body.arguments || {};\n const requestPageId = typeof args.pageId === 'string' ? args.pageId : undefined;\n if (requestPageId) {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageEntry = pageRegistry.get(requestPageId);\n if (pageEntry) {\n browserService.recordBrowserActivity(pageEntry.browserId, requestPageId);\n }\n }\n\n const errorMessage = getResultError(result);\n if (errorMessage) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n telemetry.log('error', 'browse-tool HTTP execute failed', {\n attributes: {\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': requestPageId,\n },\n });\n } else {\n telemetry.log('info', 'browse-tool HTTP execute succeeded', {\n attributes: {\n 'http.route': '/execute',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': requestPageId,\n },\n });\n }\n\n return c.json<ExecuteResponse>({\n success: !result.isError,\n result,\n error: errorMessage,\n });\n },\n );\n } catch (error) {\n telemetry.log('error', 'browse-tool HTTP execute request crashed', {\n attributes: {\n 'http.route': '/execute',\n },\n exception: error,\n });\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // List browsers endpoint\n app.get('/browsers', (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const browsers = browserService.listBrowsers();\n\n return c.json({\n browsers: browsers.map((b: BrowserInstance) => ({\n id: b.id,\n profileName: b.profileName,\n pageIds: Array.from(b.pageIds),\n currentPageId: b.currentPageId,\n createdAt: b.createdAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // List tools endpoint\n app.get('/tools', (c) => {\n try {\n const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n return c.json({\n tools: tools.map((t) => t.getDefinition()),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n app.get('/custom-tools', async (c) => {\n const directory = c.req.query('dir');\n\n if (!directory) {\n return c.json<CustomToolsResponse>({ tools: [], error: 'Missing \"dir\" query parameter' }, 400);\n }\n\n try {\n const tools = await customToolService.listTools(directory);\n return c.json<CustomToolsResponse>({ tools });\n } catch (error) {\n return c.json<CustomToolsResponse>(\n {\n tools: [],\n error: error instanceof Error ? error.message : String(error),\n },\n 400,\n );\n }\n });\n\n app.post('/custom-tools', async (c) => {\n const directory = c.req.query('dir');\n\n if (!directory) {\n return c.json<ExecuteResponse>({ success: false, error: 'Missing \"dir\" query parameter' }, 400);\n }\n\n try {\n const body = (await c.req.json()) as ExecuteRequest;\n\n if (!body.tool) {\n return c.json<ExecuteResponse>({ success: false, error: 'Missing \"tool\" field in request body' }, 400);\n }\n\n const pageId = typeof body.arguments?.pageId === 'string' ? body.arguments.pageId : undefined;\n\n return await telemetry.runInSpan(\n 'browse_tool.http.custom_tool.execute',\n {\n attributes: {\n 'http.method': 'POST',\n 'http.route': '/custom-tools',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n 'browse_tool.custom_tools.directory': directory,\n },\n },\n async (span) => {\n const result = await customToolService.executeTool(directory, body.tool, body.arguments || {});\n if (pageId) {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageEntry = pageRegistry.get(pageId);\n if (pageEntry) {\n browserService.recordBrowserActivity(pageEntry.browserId, pageId);\n }\n }\n\n const errorMessage = getResultError(result);\n if (errorMessage) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n telemetry.log('error', 'browse-tool custom tool execution failed', {\n attributes: {\n 'http.route': '/custom-tools',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n },\n });\n } else {\n telemetry.log('info', 'browse-tool custom tool execution succeeded', {\n attributes: {\n 'http.route': '/custom-tools',\n 'browse_tool.tool.name': body.tool,\n 'browse_tool.page.id': pageId,\n },\n });\n }\n\n return c.json<ExecuteResponse>({\n success: !result.isError,\n result,\n error: errorMessage,\n });\n },\n );\n } catch (error) {\n telemetry.log('error', 'browse-tool custom tool request crashed', {\n attributes: {\n 'http.route': '/custom-tools',\n 'browse_tool.custom_tools.directory': directory,\n },\n exception: error,\n });\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // Mount extension routes for Chrome extension communication\n const extensionRouter = createExtensionRoutes(container);\n app.route('/extension', extensionRouter);\n\n // Mount dashboard API routes\n const apiRouter = createApiRouter(container);\n app.route('/api', apiRouter);\n\n // Mount dashboard UI routes (SSR)\n const dashboardRouter = createDashboardRouter(container);\n app.route('/', dashboardRouter);\n\n return app;\n}\n","/**\n * WebSocket Routes for Extension Communication\n *\n * DESIGN PATTERNS:\n * - WebSocket upgrade handler using @hono/node-ws\n * - Dependency injection with InversifyJS Container\n * - Pub/sub pattern via WebSocketHub\n *\n * CODING STANDARDS:\n * - Use Hono WebSocket helpers for upgrade\n * - Keep route handlers thin, delegate to WebSocketHub\n * - Handle connection lifecycle properly\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n * - Memory leaks from uncleaned connections\n */\n\nimport type { Hono } from 'hono';\nimport type { UpgradeWebSocket } from 'hono/ws';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\n\n/**\n * Setup WebSocket routes for extension communication\n *\n * @param app - Hono app instance\n * @param container - InversifyJS container with services\n * @param upgradeWebSocket - WebSocket upgrade function from @hono/node-ws\n */\nexport function setupWebSocketRoutes(app: Hono, container: Container, upgradeWebSocket: UpgradeWebSocket): void {\n /**\n * WebSocket endpoint for extension connections\n * Path: /ws/extension/:browserId\n */\n app.get(\n '/ws/extension/:browserId',\n upgradeWebSocket((c) => {\n const browserId = c.req.param('browserId');\n let connectionId: string | null = null;\n\n return {\n onOpen(_event, ws) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n connectionId = wsHub.addConnection(ws, browserId);\n } catch {\n ws.close(1011, 'Internal server error');\n }\n },\n\n onMessage(event) {\n if (!connectionId) {\n return;\n }\n\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const data = typeof event.data === 'string' ? event.data : event.data.toString();\n wsHub.handleMessage(connectionId, data);\n } catch {\n // Error handling delegated to WebSocketHub\n }\n },\n\n onClose() {\n if (connectionId) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n wsHub.removeConnection(connectionId);\n } catch {\n // Ignore cleanup errors\n }\n }\n },\n\n onError() {\n if (connectionId) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n wsHub.removeConnection(connectionId);\n } catch {\n // Ignore cleanup errors\n }\n }\n },\n };\n }),\n );\n\n /**\n * GET /ws/stats\n * Get WebSocket connection statistics\n */\n app.get('/ws/stats', (c) => {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const stats = wsHub.getStats();\n\n return c.json({\n totalConnections: stats.totalConnections,\n browserCount: stats.browserCount,\n connections: stats.connections.map((conn) => ({\n id: conn.id,\n browserId: conn.browserId,\n connectedAt: conn.connectedAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n}\n","/**\n * HTTP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n * - Dependency injection with InversifyJS Container\n * - Graceful shutdown pattern for SIGINT/SIGTERM\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n * - Implement graceful shutdown for server cleanup\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n * - Missing signal handlers for graceful shutdown\n */\n\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createHttpContainer } from '../container/index.js';\nimport { createHttpServer } from '../server/http.js';\nimport { setupWebSocketRoutes } from '../server/websocket-routes.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IExtensionSessionRegistry } from '../services/ExtensionSessionRegistry.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR, type IIdleCleanupService } from '../services/IdleCleanupService.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\nimport { DEFAULT_MCP_PORT, buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\ninterface HttpServeOptions {\n port: string;\n headless: boolean;\n idleTimeout: string;\n host: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n profilesDir?: string;\n snippetsDir?: string;\n}\n\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\n\nfunction resolveWorkspaceRoot(startPath = process.cwd()): string {\n let current = path.resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (existsSync(path.join(current, marker))) {\n return current;\n }\n }\n\n const parent = path.dirname(current);\n if (parent === current) {\n return process.cwd();\n }\n current = parent;\n }\n}\n\n/**\n * Start HTTP server for browser automation\n */\nexport const httpServeCommand = new Command('http-serve')\n .description('Start HTTP server for browser automation')\n .option('-p, --port <port>', 'Port to listen on', String(DEFAULT_MCP_PORT))\n .option('--host <host>', 'Host to bind', getPlaywrightHost())\n .option('--headless', 'Run browsers in headless mode by default', true)\n .option('--idle-timeout <minutes>', 'Idle timeout in minutes before closing browsers', '30')\n .option('--registry-dir <path>', 'Custom registry path or directory for service discovery')\n .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n .option('--pids-dir <path>', 'Custom PIDs directory for process tracking (deprecated)')\n .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n .option('--snippets-dir <path>', 'Directory used by browser_run_code to save and load reusable snippets')\n .action(async function (this: Command, options: HttpServeOptions) {\n const commandDefaults = getCommandConfig<{\n port?: number;\n headless?: boolean;\n idleTimeout?: number;\n host?: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n profilesDir?: string;\n snippetsDir?: string;\n }>('httpServe');\n const resolvedOptions: HttpServeOptions = {\n port: resolveConfiguredOption(\n this,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n ),\n headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n idleTimeout: resolveConfiguredOption(\n this,\n 'idleTimeout',\n options.idleTimeout,\n commandDefaults.idleTimeout !== undefined ? String(commandDefaults.idleTimeout) : undefined,\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR],\n ),\n host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n registryDir: resolveConfiguredOption(\n this,\n 'registryDir',\n options.registryDir,\n commandDefaults.registryDir,\n process.env.PLAYWRIGHT_REGISTRY_DIR,\n ),\n registryPath: resolveConfiguredOption(\n this,\n 'registryPath',\n options.registryPath,\n commandDefaults.registryPath,\n process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n ),\n pidsDir: resolveConfiguredOption(\n this,\n 'pidsDir',\n options.pidsDir,\n commandDefaults.pidsDir,\n process.env.PLAYWRIGHT_PIDS_DIR,\n ),\n profilesDir: resolveConfiguredOption(\n this,\n 'profilesDir',\n options.profilesDir,\n commandDefaults.profilesDir,\n process.env.PLAYWRIGHT_PROFILES_DIR,\n ),\n snippetsDir: resolveConfiguredOption(\n this,\n 'snippetsDir',\n options.snippetsDir,\n commandDefaults.snippetsDir,\n process.env.BROWSE_TOOL_SNIPPETS_DIR,\n ),\n };\n try {\n const requestedPort = Number.parseInt(resolvedOptions.port, 10);\n const serviceName = 'browse-tool-http';\n const environment = process.env.NODE_ENV || 'development';\n const repositoryPath = resolveWorkspaceRoot(process.cwd());\n\n // Set environment variables from CLI options\n const registryPath =\n resolvedOptions.registryPath || resolvedOptions.registryDir || process.env.PLAYWRIGHT_REGISTRY_PATH;\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n process.env.PLAYWRIGHT_HOST = resolvedOptions.host;\n process.env.PLAYWRIGHT_PORT = resolvedOptions.port;\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR] = resolvedOptions.idleTimeout;\n\n if (resolvedOptions.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = resolvedOptions.pidsDir;\n }\n if (resolvedOptions.profilesDir) {\n process.env.PLAYWRIGHT_PROFILES_DIR = resolvedOptions.profilesDir;\n }\n if (resolvedOptions.snippetsDir) {\n process.env.BROWSE_TOOL_SNIPPETS_DIR = path.resolve(resolvedOptions.snippetsDir);\n }\n\n console.log('Starting HTTP server for browser automation...');\n console.log(` Port: ${requestedPort}`);\n console.log(` Host: ${resolvedOptions.host}`);\n console.log(` Headless: ${resolvedOptions.headless}`);\n console.log(` Idle Timeout: ${resolvedOptions.idleTimeout} minutes`);\n if (process.env.BROWSE_TOOL_SNIPPETS_DIR) {\n console.log(` Snippets Dir: ${process.env.BROWSE_TOOL_SNIPPETS_DIR}`);\n }\n\n // Create InversifyJS container with full HTTP server services\n const container = createHttpContainer();\n\n // Create HTTP server\n const app = createHttpServer(container);\n\n // Setup WebSocket support for extension communication\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });\n setupWebSocketRoutes(app, container, upgradeWebSocket);\n\n // Configure WebSocketHub event handlers for extension communication\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n PLAYWRIGHT_TYPES.PageRegistry,\n );\n\n // Start idle cleanup service\n const idleCleanupService = container.get<IIdleCleanupService>(PLAYWRIGHT_TYPES.IdleCleanupService);\n idleCleanupService.start();\n\n wsHub.setEventHandlers({\n onSessionRegister: (connection, message) => {\n if (message.type === 'session:register') {\n const requestedBrowserId = message.payload.browserId || connection.browserId;\n const existingBrowser = browserService.getBrowser(requestedBrowserId);\n\n let browserId: string;\n let pageId: string;\n\n if (existingBrowser && (existingBrowser.mode === 'extension' || existingBrowser.mode === 'vm')) {\n browserId = existingBrowser.id;\n const existingPages = pageRegistry.findByBrowser(browserId);\n pageId = existingBrowser.currentPageId || existingPages[0]?.id || '';\n\n wsHub.reassignConnection(connection.id, browserId);\n\n if (!pageId) {\n pageId = pageRegistry.registerExtensionPage(browserId);\n existingBrowser.pageIds.add(pageId);\n existingBrowser.currentPageId = pageId;\n }\n\n console.log(`[WebSocket] Extension connected to existing browser: ${browserId}, pageId: ${pageId}`);\n } else {\n browserId = requestedBrowserId;\n browserService.registerExtensionBrowserWithId(browserId);\n pageId = pageRegistry.registerExtensionPage(browserId);\n\n const browserInstance = browserService.getBrowser(browserId);\n if (browserInstance) {\n browserInstance.pageIds.add(pageId);\n browserInstance.currentPageId = pageId;\n }\n\n console.log(`[WebSocket] Extension connected with new browser: ${browserId}, pageId: ${pageId}`);\n }\n\n const pageEntry = pageRegistry.get(pageId);\n if (pageEntry) {\n pageEntry.extensionTabId = message.payload.tabId;\n pageEntry.url = message.payload.url ?? pageEntry.url;\n }\n\n const session = sessionRegistry.register({\n browserId,\n tabId: message.payload.tabId,\n url: message.payload.url,\n metadata: {\n transport: 'websocket',\n },\n });\n wsHub.sendSessionAck(connection, message.id ?? '', session.id, session.controlMode);\n\n wsHub.broadcastPageCreated(browserId, pageId, pageRegistry.get(pageId)?.url);\n }\n },\n onTaskResult: (_connection, message) => {\n if (message.type === 'task:result') {\n const task = taskQueue.submitResult({\n taskId: message.payload.taskId,\n success: message.payload.success,\n result: message.payload.result,\n error: message.payload.error,\n });\n\n if (task?.browserId) {\n browserService.recordBrowserActivity(task.browserId, task.pageId);\n }\n }\n },\n onTabMapped: (_connection, message) => {\n if (message.type === 'tab:mapped') {\n const pageEntry = pageRegistry.get(message.payload.pageId);\n if (!pageEntry) {\n return;\n }\n\n pageEntry.extensionTabId = message.payload.tabId;\n browserService.recordBrowserActivity(pageEntry.browserId, pageEntry.id);\n }\n },\n onDisconnect: (connection) => {\n if (connection.sessionId) {\n sessionRegistry.removeSession(connection.sessionId);\n }\n console.log(`[WebSocket] Extension disconnected: ${connection.browserId}`);\n },\n });\n\n // Register service in global registry with the exact requested port.\n // Port fallback/retry logic is handled by HttpServerManager, not here.\n const portRegistry = new PortRegistryService(process.env.PORT_REGISTRY_PATH);\n\n if (registryPath) {\n console.log(` Registry path: ${registryPath}`);\n } else {\n console.log(' Registry path: default (~/.port-registry/ports.json)');\n }\n\n const result = await portRegistry.reservePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n preferredPort: requestedPort,\n pid: process.pid,\n host: resolvedOptions.host,\n force: true,\n portRange: { min: requestedPort, max: requestedPort },\n metadata: {\n healthCheckUrl: `${buildPlaywrightBaseUrl(resolvedOptions.host, requestedPort)}/health`,\n headless: resolvedOptions.headless,\n idleTimeout: resolvedOptions.idleTimeout,\n },\n });\n\n if (!result.success || !result.record) {\n throw new Error(result.error || `Failed to reserve port ${requestedPort} in global registry`);\n }\n\n const finalPort = requestedPort;\n\n // Start server with WebSocket support\n let server;\n try {\n server = serve({\n fetch: app.fetch,\n port: finalPort,\n hostname: resolvedOptions.host,\n });\n } catch (error) {\n await portRegistry.releasePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n pid: process.pid,\n });\n throw error;\n }\n\n // Inject WebSocket handler into the server\n injectWebSocket(server);\n\n const baseUrl = buildPlaywrightBaseUrl(resolvedOptions.host, finalPort);\n console.log(`HTTP server listening on ${baseUrl}`);\n console.log(` Health check: ${baseUrl}/health`);\n console.log(` Execute tool: POST ${baseUrl}/execute`);\n\n console.log('\\nPress Ctrl+C to stop the server');\n\n // Graceful shutdown handling\n const shutdown = async (signal: string) => {\n console.log(`\\n\\n${signal} received. Shutting down gracefully...`);\n\n try {\n // Stop idle cleanup\n idleCleanupService.stop();\n\n // Close server\n server.close();\n console.log('Server closed');\n\n // Close all browsers\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n await browserService.closeAll();\n console.log('All browsers closed');\n } catch {\n // Browser service may not have browsers\n }\n\n // Deregister service\n try {\n await portRegistry.releasePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n pid: process.pid,\n });\n console.log('Service deregistered');\n } catch {\n // Ignore errors during cleanup\n }\n\n console.log('Goodbye!');\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n // Register signal handlers\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n } catch (error) {\n console.error('Error starting HTTP server:', error);\n process.exit(1);\n }\n });\n","/**\n * Tool tag definitions for filtering MCP tools by category.\n *\n * DESIGN PATTERNS:\n * - Centralized tag registry pattern\n * - Maps tool names to tag arrays for proxy-level filtering\n * - Tags are additive — a tool can belong to multiple categories\n *\n * CODING STANDARDS:\n * - Keep tag names lowercase, single-word where possible\n * - Keep map in sync with tool registrations in container\n * - Add new tools here when creating them\n *\n * AVOID:\n * - Defining tags in individual tool files (single source of truth)\n * - Using tags not listed in AVAILABLE_TAGS\n */\n\n/** All recognized tag values */\nexport const AVAILABLE_TAGS = [\n 'input',\n 'navigation',\n 'snapshot',\n 'page',\n 'dialog',\n 'network',\n 'console',\n 'script',\n 'emulation',\n 'testing',\n 'tracing',\n 'profile',\n 'browser',\n 'spec',\n 'code',\n] as const;\n\nexport type ToolTag = (typeof AVAILABLE_TAGS)[number];\n\n/** Maps each tool name to its tags */\nexport const TOOL_TAGS: Record<string, ToolTag[]> = {\n // Input tools\n browser_click: ['input'],\n browser_fill: ['input'],\n browser_type: ['input'],\n browser_select: ['input'],\n browser_hover: ['input'],\n browser_drag: ['input'],\n browser_press_key: ['input'],\n browser_upload_file: ['input'],\n // Navigation tools\n browser_navigate: ['navigation'],\n browser_go_back: ['navigation'],\n browser_go_forward: ['navigation'],\n browser_reload: ['navigation'],\n browser_wait_for: ['navigation'],\n // Snapshot tools\n browser_snapshot: ['snapshot'],\n browser_screenshot: ['snapshot'],\n browser_pdf: ['snapshot'],\n // Page management tools\n browser_list_pages: ['page'],\n browser_new_page: ['page'],\n browser_select_page: ['page'],\n browser_close_page: ['page'],\n browser_resize_page: ['page', 'emulation'],\n // Dialog tools\n browser_handle_dialog: ['dialog'],\n // Network tools\n browser_list_network_requests: ['network'],\n browser_get_network_request: ['network'],\n // Console tools\n browser_list_console_messages: ['console'],\n // Script tools\n browser_evaluate_script: ['script'],\n // Emulation tools\n browser_emulate: ['emulation'],\n // Testing and tracing tools\n browser_expect: ['testing'],\n browser_start_trace: ['tracing'],\n browser_stop_trace: ['tracing'],\n // Profile management tools\n browser_list_profiles: ['profile'],\n browser_delete_profile: ['profile'],\n // Spec tools\n run_spec: ['spec', 'testing'],\n discover_specs: ['spec', 'testing'],\n // Browser lifecycle tools\n browser_launch: ['browser'],\n browser_close: ['browser'],\n // Code execution tools\n browser_run_code: ['code', 'script'],\n};\n\n/**\n * Resolves a set of tag names to matching tool names.\n * @param tags - Tags to include\n * @returns Set of tool names that match any of the given tags\n */\nexport function getToolNamesByTags(tags: string[]): Set<string> {\n const result = new Set<string>();\n for (const [toolName, toolTags] of Object.entries(TOOL_TAGS)) {\n if (toolTags.some((t) => tags.includes(t))) {\n result.add(toolName);\n }\n }\n return result;\n}\n","/**\n * Proxy MCP Server\n *\n * DESIGN PATTERNS:\n * - Proxy pattern for forwarding requests to HTTP server\n * - MCP Server setup pattern with request handlers\n * - Session tracking for cleanup on shutdown\n *\n * CODING STANDARDS:\n * - Create server instance using Server from @modelcontextprotocol/sdk\n * - Use proper error handling in request handlers\n * - Export factory function for server creation\n * - Track browsers/pages for session cleanup\n *\n * AVOID:\n * - Business logic in handlers\n * - Hardcoded URLs or configuration\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { getToolNamesByTags } from '../constants/tool-tags.js';\nimport type { IMcpSessionTracker } from '../services/McpSessionTracker.js';\nimport type { ToolDefinition } from '../types/index.js';\n\n/** Browser launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/**\n * Tool filtering configuration for proxy server.\n * When tags are specified, only tools matching those tags are included.\n * Excluded tools are then removed from the result.\n */\nexport interface ToolFilterConfig {\n /** Include only tools matching these tags */\n tags?: string[];\n /** Exclude specific tools by name (applied after tag filtering) */\n exclude?: string[];\n}\n\n/**\n * Configuration for the proxy server\n */\nexport interface ProxyServerConfig {\n /** Base URL of the HTTP server to proxy requests to */\n httpBaseUrl: string;\n /** Session tracker for tracking browsers/pages created in this session */\n sessionTracker?: IMcpSessionTracker;\n /** Default launch mode for browser_launch tool when not specified */\n defaultMode?: LaunchMode;\n /** Tool filtering options */\n toolFilter?: ToolFilterConfig;\n /** Optional directory containing custom tool scripts */\n customToolsDir?: string;\n /** Pre-selected profile enforced for this MCP session */\n enforcedProfileName?: string;\n}\n\n/**\n * Response from the /tools endpoint\n */\ninterface ToolsResponse {\n tools: ToolDefinition[];\n error?: string;\n}\n\ninterface CustomToolDescriptor {\n name: string;\n description: string;\n suggestionActions?: string;\n inputSchema: ToolDefinition['inputSchema'];\n capabilities?: Record<string, unknown>;\n}\n\ninterface CustomToolsResponse {\n tools: CustomToolDescriptor[];\n error?: string;\n}\n\n/**\n * Response from the /execute endpoint\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: {\n content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n isError?: boolean;\n };\n error?: string;\n}\n\ninterface ProfileListResponse {\n profileCount?: number;\n profiles?: Array<{\n name?: string;\n browserType?: string;\n viewport?: unknown;\n userAgent?: string | null;\n locale?: string | null;\n timezone?: string | null;\n colorScheme?: string | null;\n createdAt?: string;\n updatedAt?: string;\n }>;\n}\n\nconst TOOLS_REQUEST_TIMEOUT_MS = 3000;\nconst TOOLS_REQUEST_MAX_ATTEMPTS = 4;\nconst TOOLS_REQUEST_RETRY_BASE_DELAY_MS = 150;\n\n/**\n * Creates a proxy MCP server that forwards all requests to an HTTP server.\n *\n * This server acts as a thin proxy layer:\n * - ListTools: Fetches tool definitions from HTTP server's /tools endpoint\n * - CallTool: Forwards tool execution to HTTP server's /execute endpoint\n *\n * @param config - Proxy server configuration\n * @returns Configured MCP Server instance\n */\n/** Tools that create browsers */\nconst BROWSER_CREATING_TOOLS = ['browser_launch'];\n/** Tools that create pages */\nconst PAGE_CREATING_TOOLS = ['browser_new_page'];\n\n/**\n * Parse browser/page info from tool result\n */\nfunction parseResourceFromResult(\n result: ExecuteResponse['result'],\n): { browserId?: string; pageId?: string; mode?: string; url?: string } | null {\n if (!result?.content?.[0]?.text) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(result.content[0].text);\n return {\n browserId: parsed.browserId,\n pageId: parsed.pageId,\n mode: parsed.mode,\n url: parsed.url,\n };\n } catch {\n return null;\n }\n}\n\nasync function wait(delayMs: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n}\n\nasync function fetchToolsFromHttpServer(httpBaseUrl: string): Promise<ToolDefinition[]> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TOOLS_REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(`${httpBaseUrl}/tools`, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as ToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n if (!Array.isArray(data.tools)) {\n throw new Error('Invalid /tools response: missing tools array');\n }\n\n if (data.tools.length === 0) {\n throw new Error('HTTP server returned an empty tool list');\n }\n\n return data.tools;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction buildCustomToolsUrl(httpBaseUrl: string, customToolsDir: string): string {\n const url = new URL('/custom-tools', httpBaseUrl);\n url.searchParams.set('dir', customToolsDir);\n return url.toString();\n}\n\nasync function fetchCustomToolsFromHttpServer(\n httpBaseUrl: string,\n customToolsDir: string,\n): Promise<CustomToolDescriptor[]> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TOOLS_REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(buildCustomToolsUrl(httpBaseUrl, customToolsDir), { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as CustomToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n if (!Array.isArray(data.tools)) {\n throw new Error('Invalid /custom-tools response: missing tools array');\n }\n\n return data.tools;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nasync function fetchToolsWithRetry(httpBaseUrl: string): Promise<ToolDefinition[]> {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= TOOLS_REQUEST_MAX_ATTEMPTS; attempt += 1) {\n try {\n return await fetchToolsFromHttpServer(httpBaseUrl);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < TOOLS_REQUEST_MAX_ATTEMPTS) {\n await wait(TOOLS_REQUEST_RETRY_BASE_DELAY_MS * attempt);\n }\n }\n }\n\n throw new Error(\n `Failed to fetch tools after ${TOOLS_REQUEST_MAX_ATTEMPTS} attempts: ${lastError?.message ?? 'Unknown error'}`,\n { cause: lastError },\n );\n}\n\nasync function fetchCustomToolsWithRetry(httpBaseUrl: string, customToolsDir: string): Promise<CustomToolDescriptor[]> {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= TOOLS_REQUEST_MAX_ATTEMPTS; attempt += 1) {\n try {\n return await fetchCustomToolsFromHttpServer(httpBaseUrl, customToolsDir);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < TOOLS_REQUEST_MAX_ATTEMPTS) {\n await wait(TOOLS_REQUEST_RETRY_BASE_DELAY_MS * attempt);\n }\n }\n }\n\n throw new Error(\n `Failed to fetch custom tools after ${TOOLS_REQUEST_MAX_ATTEMPTS} attempts: ${lastError?.message ?? 'Unknown error'}`,\n { cause: lastError },\n );\n}\n\n/**\n * Build the set of allowed tool names based on filter config.\n * Returns null if no filtering is configured (all tools allowed).\n */\nfunction buildAllowedToolNames(filter?: ToolFilterConfig): Set<string> | null {\n if (!filter?.tags?.length && !filter?.exclude?.length) {\n return null;\n }\n\n const taggedNames = filter.tags?.length ? getToolNamesByTags(filter.tags) : null;\n const excludeSet = new Set(filter.exclude ?? []);\n\n if (!taggedNames) {\n // Only exclude, no tag filter — return null to allow all, exclusion applied separately\n return excludeSet.size > 0 ? excludeSet : null;\n }\n\n // Remove excluded tools from the tag-matched set\n for (const name of excludeSet) {\n taggedNames.delete(name);\n }\n\n return taggedNames;\n}\n\nexport function createProxyServer(config: ProxyServerConfig): Server {\n const { httpBaseUrl, sessionTracker, defaultMode, toolFilter, customToolsDir, enforcedProfileName } = config;\n\n // Pre-compute allowed tool names for filtering\n const allowedToolNames = toolFilter?.tags?.length ? buildAllowedToolNames(toolFilter) : null;\n const excludedToolNames = new Set(toolFilter?.exclude ?? []);\n const hasFilter = allowedToolNames !== null || excludedToolNames.size > 0;\n\n const server = new Server(\n {\n name: 'browse-tool',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Session-specific tool definition\n const sessionToolDefinition: ToolDefinition = {\n name: 'browser_list_session',\n description:\n 'List all browsers and pages created in this MCP session. Use this to see what resources you have available.',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n\n let cachedTools: ToolDefinition[] = [];\n let cachedCustomTools: CustomToolDescriptor[] = [];\n let cachedBuiltinToolNames: Set<string> = new Set();\n\n /**\n * Apply tool filtering based on --tags and --exclude options.\n * When tags are specified, only tools matching those tags are included.\n * Excluded tools are then removed regardless.\n */\n function filterTools(tools: ToolDefinition[]): ToolDefinition[] {\n if (!hasFilter) {\n return tools;\n }\n\n return tools.filter((tool) => {\n if (excludedToolNames.has(tool.name)) {\n return false;\n }\n if (allowedToolNames !== null) {\n return allowedToolNames.has(tool.name);\n }\n return true;\n });\n }\n\n function filterCustomTools(tools: CustomToolDescriptor[]): CustomToolDescriptor[] {\n return tools.filter((tool) => !excludedToolNames.has(tool.name));\n }\n\n function applyEnforcedProfileToLaunchArgs(args: Record<string, unknown>): Record<string, unknown> {\n if (!enforcedProfileName) {\n return args;\n }\n\n return { ...args, profileName: enforcedProfileName };\n }\n\n function rewriteProfileListResult(\n result: NonNullable<ExecuteResponse['result']>,\n ): NonNullable<ExecuteResponse['result']> {\n if (!enforcedProfileName) {\n return result;\n }\n\n const text = result?.content?.[0]?.text;\n if (!text) {\n return result;\n }\n\n try {\n const parsed = JSON.parse(text) as ProfileListResponse;\n const profiles = Array.isArray(parsed.profiles) ? parsed.profiles : [];\n const filteredProfiles = profiles.filter((profile) => profile?.name === enforcedProfileName);\n\n return {\n ...result,\n content: [\n {\n ...result.content[0],\n text: JSON.stringify(\n {\n ...parsed,\n profileCount: filteredProfiles.length,\n profiles: filteredProfiles,\n },\n null,\n 2,\n ),\n },\n ...result.content.slice(1),\n ],\n };\n } catch {\n return result;\n }\n }\n\n function toMcpToolDefinition(tool: CustomToolDescriptor): ToolDefinition {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n annotations: tool.capabilities,\n };\n }\n\n /**\n * Check if a tool name is allowed by the current filter.\n */\n function isToolAllowed(toolName: string): boolean {\n if (excludedToolNames.has(toolName)) {\n return false;\n }\n if (allowedToolNames !== null) {\n return allowedToolNames.has(toolName);\n }\n return true;\n }\n\n /**\n * ListTools handler - fetches tool definitions from HTTP server + session tool\n */\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n try {\n const toolsFromServer = await fetchToolsWithRetry(httpBaseUrl);\n cachedTools = toolsFromServer;\n cachedBuiltinToolNames = new Set(toolsFromServer.map((t) => t.name));\n const customTools = customToolsDir ? await fetchCustomToolsWithRetry(httpBaseUrl, customToolsDir) : [];\n cachedCustomTools = customTools;\n\n // Filter tools based on --tags and --exclude\n const tools = [\n ...filterTools(toolsFromServer),\n ...filterCustomTools(customTools).map((tool) => toMcpToolDefinition(tool)),\n ];\n\n // Add session-specific tool if tracker is available\n if (sessionTracker) {\n tools.push(sessionToolDefinition);\n }\n\n return { tools };\n } catch (error) {\n if (cachedTools.length > 0 || cachedCustomTools.length > 0) {\n console.error(\n 'Failed to refresh tools from HTTP server. Returning cached tools:',\n error instanceof Error ? error.message : String(error),\n );\n\n const tools = [\n ...filterTools(cachedTools),\n ...filterCustomTools(cachedCustomTools).map((tool) => toMcpToolDefinition(tool)),\n ];\n if (sessionTracker) {\n tools.push(sessionToolDefinition);\n }\n return { tools };\n }\n\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to list tools from HTTP server at ${httpBaseUrl}: ${message}`, { cause: error });\n }\n });\n\n /**\n * CallTool handler - forwards tool execution to HTTP server\n */\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n let customToolNames = new Set(cachedCustomTools.map((tool) => tool.name));\n\n // Reject filtered-out tools\n if (name !== 'browser_list_session' && !isToolAllowed(name)) {\n return {\n content: [{ type: 'text', text: `Tool \"${name}\" is not available (filtered by --tags or --exclude)` }],\n isError: true,\n };\n }\n\n // Handle session-specific tool locally\n if (name === 'browser_list_session' && sessionTracker) {\n const state = sessionTracker.getSessionState();\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(state, null, 2),\n },\n ],\n };\n }\n\n // Prepare arguments, injecting defaults where needed\n let finalArgs = (args || {}) as Record<string, unknown>;\n\n // Enforce default mode for browser_launch when configured\n // When default mode is set, always use it (don't allow override)\n if (name === 'browser_launch' && defaultMode) {\n finalArgs = { ...finalArgs, mode: defaultMode };\n }\n if (name === 'browser_launch') {\n finalArgs = applyEnforcedProfileToLaunchArgs(finalArgs);\n }\n\n try {\n // Only re-fetch custom tools when the tool name is not a known built-in\n // and not already cached as a custom tool (i.e. it might be a newly-added custom tool).\n // Fall back to cached custom tools on fetch failure to avoid blocking built-in tool calls.\n if (customToolsDir && !customToolNames.has(name) && !cachedBuiltinToolNames.has(name)) {\n try {\n cachedCustomTools = await fetchCustomToolsWithRetry(httpBaseUrl, customToolsDir);\n customToolNames = new Set(cachedCustomTools.map((tool) => tool.name));\n } catch (refreshError) {\n console.error(\n `Failed to refresh custom tools, using cached list: ${refreshError instanceof Error ? refreshError.message : String(refreshError)}`,\n );\n }\n }\n\n const isCustomTool = customToolNames.has(name);\n const response = await fetch(\n isCustomTool && customToolsDir ? buildCustomToolsUrl(httpBaseUrl, customToolsDir) : `${httpBaseUrl}/execute`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ tool: name, arguments: finalArgs }),\n },\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n content: [{ type: 'text', text: `HTTP error ${response.status}: ${errorText}` }],\n isError: true,\n };\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n const errorText = data.error || data.result?.content?.[0]?.text || 'Unknown error from HTTP server';\n return {\n content: [{ type: 'text', text: errorText }],\n isError: true,\n };\n }\n\n // Track browsers/pages created in this session\n if (sessionTracker && data.result) {\n if (BROWSER_CREATING_TOOLS.includes(name)) {\n const resource = parseResourceFromResult(data.result);\n if (resource?.browserId) {\n sessionTracker.trackBrowser(\n resource.browserId,\n (resource.mode as 'playwright' | 'extension' | 'vm' | 'stealth') || 'playwright',\n );\n if (resource.pageId) {\n sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n }\n }\n } else if (PAGE_CREATING_TOOLS.includes(name)) {\n const resource = parseResourceFromResult(data.result);\n if (resource?.pageId && resource?.browserId) {\n sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n }\n }\n }\n\n if (name === 'browser_list_profiles' && data.result) {\n return rewriteProfileListResult(data.result);\n }\n\n return data.result || { content: [{ type: 'text', text: 'No result returned' }] };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Failed to execute tool via HTTP server: ${errorMessage}` }],\n isError: true,\n };\n }\n });\n\n return server;\n}\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { PLAYWRIGHT_TYPES, createMcpContainer } from '../container/index.js';\nimport type { ToolFilterConfig } from '../server/proxy.js';\nimport { createProxyServer } from '../server/proxy.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR } from '../services/IdleCleanupService.js';\nimport { McpSessionTracker } from '../services/McpSessionTracker.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\nimport { StreamableHttpTransportHandler } from '../transports/streamable-http.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost, getPlaywrightPort } from '../utils/networkConfig.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst TRANSPORT_STDIO = 'stdio';\nconst TRANSPORT_HTTP = 'http';\nconst TRANSPORT_STREAMABLE_HTTP = 'streamable-http';\nconst SUPPORTED_TRANSPORTS = new Set([TRANSPORT_STDIO, TRANSPORT_HTTP, TRANSPORT_STREAMABLE_HTTP]);\n\nconst BROWSER_CHROMIUM = 'chromium';\nconst BROWSER_FIREFOX = 'firefox';\nconst BROWSER_WEBKIT = 'webkit';\nconst SUPPORTED_BROWSERS = new Set([BROWSER_CHROMIUM, BROWSER_FIREFOX, BROWSER_WEBKIT]);\n\nconst MODE_PLAYWRIGHT = 'playwright';\nconst MODE_EXTENSION = 'extension';\nconst MODE_VM = 'vm';\nconst MODE_STEALTH = 'stealth';\nconst SUPPORTED_LAUNCH_MODES = new Set([MODE_PLAYWRIGHT, MODE_EXTENSION, MODE_VM, MODE_STEALTH]);\n\nconst BROWSER_CLOSE_TOOL = 'browser_close';\nconst CSV_SEPARATOR = ',';\n\nconst HTTP_STATUS_SPAWNED = 'spawned';\nconst HTTP_STATUS_REUSED = 'reused';\n\nconst SIGNAL_SIGINT = 'SIGINT';\nconst SIGNAL_SIGTERM = 'SIGTERM';\n\nconst ENV_REGISTRY_DIR = 'PLAYWRIGHT_REGISTRY_DIR';\nconst ENV_PORT_REGISTRY_PATH = 'PORT_REGISTRY_PATH';\nconst ENV_PIDS_DIR = 'PLAYWRIGHT_PIDS_DIR';\nconst ENV_PROFILES_DIR = 'PLAYWRIGHT_PROFILES_DIR';\nconst ENV_HOST = 'PLAYWRIGHT_HOST';\nconst ENV_PORT = 'PLAYWRIGHT_PORT';\nconst ENV_MCP_PORT = 'PLAYWRIGHT_MCP_PORT';\nconst PROFILE_HEADER = 'x-profile';\n\nconst EXIT_CODE_SUCCESS = 0;\nconst EXIT_CODE_FAILURE = 1;\nconst DEFAULT_STREAMABLE_HTTP_PORT = 3201;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Supported browser types */\nexport type BrowserType = 'chromium' | 'firefox' | 'webkit';\n\n/** Supported launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/** CLI options for mcp-serve command */\nexport interface McpServeOptions {\n type: string;\n browser: BrowserType;\n headless: boolean;\n profile?: string;\n mode?: LaunchMode;\n registryPath?: string;\n host?: string;\n port?: string | number;\n registryDir?: string;\n pidsDir?: string;\n profilesDir?: string;\n tags?: string;\n exclude?: string;\n customTools?: string;\n snippetsDir?: string;\n idleTimeout?: string;\n}\n\n/** Interface for transport handlers */\ninterface TransportHandler {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\nexport class InvalidTransportError extends Error {\n readonly code = 'INVALID_TRANSPORT';\n readonly recovery = `Use --type ${[...SUPPORTED_TRANSPORTS].join(', ')}`;\n readonly transportType: string;\n\n constructor(transportType: string, options?: ErrorOptions) {\n super(`Unknown transport type: ${transportType}`, options);\n this.name = 'InvalidTransportError';\n this.transportType = transportType;\n }\n}\n\nexport class InvalidBrowserTypeError extends Error {\n readonly code = 'INVALID_BROWSER_TYPE';\n readonly recovery = `Use --browser ${[...SUPPORTED_BROWSERS].join(', ')}`;\n readonly browserType: string;\n\n constructor(browserType: string, options?: ErrorOptions) {\n super(`Unknown browser type: ${browserType}`, options);\n this.name = 'InvalidBrowserTypeError';\n this.browserType = browserType;\n }\n}\n\nexport class InvalidLaunchModeError extends Error {\n readonly code = 'INVALID_LAUNCH_MODE';\n readonly recovery = `Use --mode ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`;\n readonly launchMode: string;\n\n constructor(launchMode: string, options?: ErrorOptions) {\n super(`Unknown launch mode: ${launchMode}`, options);\n this.name = 'InvalidLaunchModeError';\n this.launchMode = launchMode;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction parseBrowserType(value: string): BrowserType {\n const browserType = value.toLowerCase();\n if (!SUPPORTED_BROWSERS.has(browserType)) {\n throw new InvalidBrowserTypeError(value);\n }\n return browserType as BrowserType;\n}\n\nfunction parseLaunchMode(value: string): LaunchMode {\n const launchMode = value.toLowerCase();\n if (!SUPPORTED_LAUNCH_MODES.has(launchMode)) {\n throw new InvalidLaunchModeError(value);\n }\n return launchMode as LaunchMode;\n}\n\nfunction parsePort(value: string | number): number {\n const port = typeof value === 'number' ? value : Number.parseInt(value, 10);\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new Error(`Invalid port: ${value}`);\n }\n return port;\n}\n\nfunction normalizeTransportType(value: string): string {\n const transportType = value.toLowerCase();\n return transportType === TRANSPORT_HTTP ? TRANSPORT_STREAMABLE_HTTP : transportType;\n}\n\nfunction getHeaderValue(headers: Record<string, string | string[] | undefined>, name: string): string | undefined {\n const rawValue = headers[name];\n const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;\n return value?.trim() || undefined;\n}\n\n/**\n * Parse a comma-separated string into a trimmed, non-empty string array.\n */\nfunction parseCsv(value: string): string[] {\n return value\n .split(CSV_SEPARATOR)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Build a ToolFilterConfig from the raw --tags and --exclude CLI strings.\n * Returns undefined when no filtering is configured.\n */\nfunction buildToolFilter(options: McpServeOptions): ToolFilterConfig | undefined {\n const tags = options.tags ? parseCsv(options.tags) : undefined;\n const exclude = options.exclude ? parseCsv(options.exclude) : undefined;\n\n if (!tags?.length && !exclude?.length) {\n return undefined;\n }\n\n return { tags, exclude };\n}\n\n/**\n * Typed error for browser cleanup failures with structured context.\n */\nexport class BrowserCleanupError extends Error {\n readonly code = 'BROWSER_CLEANUP_FAILED';\n readonly recovery = 'Browsers may still be running; check the HTTP server or kill processes manually';\n readonly failedBrowserIds: string[];\n readonly httpBaseUrl: string;\n\n constructor(failedBrowserIds: string[], httpBaseUrl: string, options?: ErrorOptions) {\n super(`Failed to close ${failedBrowserIds.length} browser(s): ${failedBrowserIds.join(', ')}`, options);\n this.name = 'BrowserCleanupError';\n this.failedBrowserIds = failedBrowserIds;\n this.httpBaseUrl = httpBaseUrl;\n }\n}\n\n/**\n * Typed error for shutdown failures with structured context.\n */\nexport class SessionShutdownError extends Error {\n readonly code = 'SESSION_SHUTDOWN_FAILED';\n readonly recovery = 'Check logs for cleanup and transport errors; processes may need manual cleanup';\n readonly signal: string;\n\n constructor(signal: string, options?: ErrorOptions) {\n super(`Shutdown failed for signal ${signal}`, options);\n this.name = 'SessionShutdownError';\n this.signal = signal;\n }\n}\n\n/**\n * Typed error for HTTP server startup failures.\n */\nexport class HttpServerStartError extends Error {\n readonly code = 'HTTP_SERVER_START_FAILED';\n readonly recovery = 'Check if the HTTP server binary exists and the port is available';\n\n constructor(options?: ErrorOptions) {\n super('Failed to start HTTP server', options);\n this.name = 'HttpServerStartError';\n }\n}\n\n/**\n * Typed error for MCP server bootstrap failures.\n */\nexport class McpServerBootstrapError extends Error {\n readonly code = 'MCP_SERVER_BOOTSTRAP_FAILED';\n readonly recovery = 'Check if all dependencies are installed and try again';\n\n constructor(options?: ErrorOptions) {\n super('Failed to start MCP server', options);\n this.name = 'McpServerBootstrapError';\n }\n}\n\n/**\n * Close browsers tracked in session via HTTP server.\n * Validates HTTP response status before reporting success.\n * Only clears tracker state when all browsers close successfully.\n */\nasync function closeSessionBrowsers(sessionTracker: McpSessionTracker, httpBaseUrl: string): Promise<void> {\n const browserIds = sessionTracker.getBrowserIds();\n const failedIds: string[] = [];\n let firstCause: Error | undefined;\n\n for (const browserId of browserIds) {\n try {\n const response = await fetch(`${httpBaseUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n tool: BROWSER_CLOSE_TOOL,\n arguments: { browserId },\n }),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(`HTTP ${response.status} from ${httpBaseUrl}/execute: ${body}`);\n }\n\n console.error(` Closed browser: ${browserId}`);\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n console.error(` Failed to close browser: ${browserId}`, cause);\n failedIds.push(browserId);\n firstCause ??= cause;\n }\n }\n\n if (failedIds.length > 0) {\n throw new BrowserCleanupError(failedIds, httpBaseUrl, { cause: firstCause });\n }\n\n sessionTracker.clear();\n}\n\n/**\n * Start MCP server with session-aware cleanup.\n * Uses process.once and an isShuttingDown guard for idempotent shutdown.\n * Always attempts handler.stop() via finally to prevent resource leaks.\n */\nasync function startServerWithSessionCleanup(\n handler: TransportHandler,\n sessionTracker: McpSessionTracker,\n httpBaseUrl: string,\n): Promise<void> {\n await handler.start();\n\n let isShuttingDown = false;\n\n const shutdown = async (signal: string) => {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n let cleanupError: Error | undefined;\n\n try {\n const state = sessionTracker.getSessionState();\n if (state.totalBrowsers > 0) {\n console.error(` Cleaning up ${state.totalBrowsers} browser(s) from this session...`);\n await closeSessionBrowsers(sessionTracker, httpBaseUrl);\n }\n } catch (error) {\n cleanupError = error instanceof Error ? error : new Error(String(error));\n console.error('Browser cleanup error:', cleanupError);\n } finally {\n try {\n await handler.stop();\n } catch (stopError) {\n const cause = stopError instanceof Error ? stopError : new Error(String(stopError));\n console.error('Transport stop error:', cause);\n cleanupError ??= cause;\n }\n\n if (cleanupError) {\n const shutdownError = new SessionShutdownError(signal, { cause: cleanupError });\n console.error('Error during shutdown:', shutdownError);\n process.exit(EXIT_CODE_FAILURE);\n }\n\n process.exit(EXIT_CODE_SUCCESS);\n }\n };\n\n process.once(SIGNAL_SIGINT, () => shutdown(SIGNAL_SIGINT));\n process.once(SIGNAL_SIGTERM, () => shutdown(SIGNAL_SIGTERM));\n}\n\n// ============================================================================\n// Command Definition\n// ============================================================================\n\nexport const mcpServeCommand = new Command('mcp-serve')\n .description('Start Playwright MCP server for browser automation')\n .option(\n '-t, --type <type>',\n `Transport type: ${[TRANSPORT_STDIO, TRANSPORT_STREAMABLE_HTTP].join(', ')}`,\n TRANSPORT_STDIO,\n )\n .option('-b, --browser <browser>', `Default browser type: ${[...SUPPORTED_BROWSERS].join(', ')}`, BROWSER_CHROMIUM)\n .option('--headless', 'Run browsers in headless mode by default', false)\n .option('--no-headless', 'Run browsers in headed mode (visible window)')\n .option('--host <host>', 'Default host for HTTP services', getPlaywrightHost())\n .option('--port <port>', 'Port for streamable HTTP transport', String(DEFAULT_STREAMABLE_HTTP_PORT))\n .option('-p, --profile <name>', 'Default profile name for browser sessions')\n .option('-m, --mode <mode>', `Default launch mode: ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`)\n .option('--tags <tags>', 'Comma-separated tags to filter tools (e.g. input,navigation,snapshot)')\n .option('--exclude <tools>', 'Comma-separated tool names to exclude (applied after --tags)')\n .option('--custom-tools <path>', 'Path to a folder containing tools.yaml and custom tool scripts')\n .option('--snippets-dir <path>', 'Path to a folder where browser_run_code snippets are stored')\n .option('--idle-timeout <minutes>', 'Idle timeout in minutes before auto-closing browsers')\n .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n .option('--registry-dir <path>', 'Custom registry directory for service discovery')\n .option('--pids-dir <path>', 'Custom PIDs directory for process tracking')\n .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n .action(async function (this: Command, options: McpServeOptions) {\n const commandDefaults = getCommandConfig<{\n type?: string;\n browser?: BrowserType;\n headless?: boolean;\n profile?: string;\n mode?: LaunchMode;\n host?: string;\n port?: string | number;\n tags?: string;\n exclude?: string;\n customTools?: string;\n snippetsDir?: string;\n idleTimeout?: number;\n registryPath?: string;\n registryDir?: string;\n pidsDir?: string;\n profilesDir?: string;\n }>('mcpServe');\n const resolvedOptions: McpServeOptions = {\n type: resolveConfiguredOption(this, 'type', options.type, commandDefaults.type),\n browser: resolveConfiguredOption(this, 'browser', options.browser, commandDefaults.browser),\n headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n profile: resolveConfiguredOption(this, 'profile', options.profile, commandDefaults.profile),\n mode: resolveConfiguredOption(this, 'mode', options.mode, commandDefaults.mode),\n host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n port: resolveConfiguredOption(this, 'port', options.port, commandDefaults.port, process.env[ENV_MCP_PORT]),\n tags: resolveConfiguredOption(this, 'tags', options.tags, commandDefaults.tags),\n exclude: resolveConfiguredOption(this, 'exclude', options.exclude, commandDefaults.exclude),\n customTools: resolveConfiguredOption(this, 'customTools', options.customTools, commandDefaults.customTools),\n snippetsDir: resolveConfiguredOption(this, 'snippetsDir', options.snippetsDir, commandDefaults.snippetsDir),\n idleTimeout: resolveConfiguredOption(\n this,\n 'idleTimeout',\n options.idleTimeout,\n commandDefaults.idleTimeout !== undefined ? String(commandDefaults.idleTimeout) : undefined,\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR],\n ),\n registryPath: resolveConfiguredOption(\n this,\n 'registryPath',\n options.registryPath,\n commandDefaults.registryPath,\n process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n ),\n registryDir: resolveConfiguredOption(\n this,\n 'registryDir',\n options.registryDir,\n commandDefaults.registryDir,\n process.env.PLAYWRIGHT_REGISTRY_DIR,\n ),\n pidsDir: resolveConfiguredOption(\n this,\n 'pidsDir',\n options.pidsDir,\n commandDefaults.pidsDir,\n process.env.PLAYWRIGHT_PIDS_DIR,\n ),\n profilesDir: resolveConfiguredOption(\n this,\n 'profilesDir',\n options.profilesDir,\n commandDefaults.profilesDir,\n process.env.PLAYWRIGHT_PROFILES_DIR,\n ),\n };\n const transportType = normalizeTransportType(resolvedOptions.type);\n\n try {\n if (!SUPPORTED_TRANSPORTS.has(transportType)) {\n throw new InvalidTransportError(transportType);\n }\n\n const browserType = parseBrowserType(resolvedOptions.browser);\n const defaultMode = resolvedOptions.mode ? parseLaunchMode(resolvedOptions.mode) : undefined;\n const transportPort = parsePort(resolvedOptions.port ?? DEFAULT_STREAMABLE_HTTP_PORT);\n const toolFilter = buildToolFilter(resolvedOptions);\n const customToolsDir = resolvedOptions.customTools ? path.resolve(resolvedOptions.customTools) : undefined;\n const snippetsDir = resolvedOptions.snippetsDir ? path.resolve(resolvedOptions.snippetsDir) : undefined;\n\n console.error('Playwright MCP Server starting...');\n console.error(` Transport: ${transportType}`);\n if (transportType === TRANSPORT_STREAMABLE_HTTP) {\n console.error(` MCP endpoint: http://${resolvedOptions.host ?? getPlaywrightHost()}:${transportPort}/mcp`);\n }\n console.error(` Default browser: ${browserType}`);\n console.error(` Headless: ${resolvedOptions.headless}`);\n if (defaultMode) {\n console.error(` Default mode: ${defaultMode}`);\n }\n if (resolvedOptions.profile) {\n console.error(` Profile: ${resolvedOptions.profile}`);\n }\n if (toolFilter?.tags?.length) {\n console.error(` Tags: ${toolFilter.tags.join(', ')}`);\n }\n if (toolFilter?.exclude?.length) {\n console.error(` Exclude: ${toolFilter.exclude.join(', ')}`);\n }\n if (customToolsDir) {\n console.error(` Custom tools: ${customToolsDir}`);\n }\n if (snippetsDir) {\n console.error(` Snippets dir: ${snippetsDir}`);\n }\n if (resolvedOptions.idleTimeout) {\n console.error(` Idle timeout: ${resolvedOptions.idleTimeout} minutes`);\n }\n\n const registryPath = resolvedOptions.registryPath || resolvedOptions.registryDir;\n if (registryPath) {\n process.env[ENV_REGISTRY_DIR] = registryPath;\n process.env[ENV_PORT_REGISTRY_PATH] = registryPath;\n console.error(` Registry path: ${registryPath}`);\n }\n if (resolvedOptions.pidsDir) {\n process.env[ENV_PIDS_DIR] = resolvedOptions.pidsDir;\n console.error(` PIDs dir: ${resolvedOptions.pidsDir}`);\n }\n if (resolvedOptions.profilesDir) {\n process.env[ENV_PROFILES_DIR] = resolvedOptions.profilesDir;\n console.error(` Profiles dir: ${resolvedOptions.profilesDir}`);\n }\n if (snippetsDir) {\n process.env.BROWSE_TOOL_SNIPPETS_DIR = snippetsDir;\n }\n if (resolvedOptions.idleTimeout) {\n process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR] = resolvedOptions.idleTimeout;\n }\n if (resolvedOptions.host) {\n process.env[ENV_HOST] = resolvedOptions.host;\n }\n process.env[ENV_PORT] = getPlaywrightPort().toString();\n\n const localContainer = createMcpContainer();\n\n const httpServerManager = localContainer.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n const httpStatus = await httpServerManager.ensureRunning();\n\n if (!httpStatus.running) {\n throw new HttpServerStartError();\n }\n\n const httpBaseUrl = buildPlaywrightBaseUrl(getPlaywrightHost(), httpStatus.port);\n const httpLabel = httpStatus.spawned ? HTTP_STATUS_SPAWNED : HTTP_STATUS_REUSED;\n console.error(` HTTP server: ${httpLabel} on port ${httpStatus.port}`);\n\n if (transportType === TRANSPORT_STDIO) {\n const sessionTracker = new McpSessionTracker();\n const server = createProxyServer({\n httpBaseUrl,\n sessionTracker,\n defaultMode,\n toolFilter,\n customToolsDir,\n enforcedProfileName: resolvedOptions.profile,\n });\n const handler = new StdioTransportHandler(server);\n\n await startServerWithSessionCleanup(handler, sessionTracker, httpBaseUrl);\n return;\n }\n\n const handler = new StreamableHttpTransportHandler(\n ({ headers }) => {\n const enforcedProfileName = getHeaderValue(headers, PROFILE_HEADER) ?? resolvedOptions.profile;\n const sessionTracker = new McpSessionTracker();\n return {\n server: createProxyServer({\n httpBaseUrl,\n sessionTracker,\n defaultMode,\n toolFilter,\n customToolsDir,\n enforcedProfileName,\n }),\n onClose: async () => {\n const state = sessionTracker.getSessionState();\n if (state.totalBrowsers > 0) {\n await closeSessionBrowsers(sessionTracker, httpBaseUrl);\n }\n },\n };\n },\n {\n host: resolvedOptions.host ?? getPlaywrightHost(),\n port: transportPort,\n },\n );\n\n await handler.start();\n\n let isShuttingDown = false;\n const shutdown = async (signal: string) => {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n await handler.stop();\n process.exit(EXIT_CODE_SUCCESS);\n } catch (stopError) {\n console.error('Transport stop error:', stopError);\n process.exit(EXIT_CODE_FAILURE);\n }\n };\n\n process.once(SIGNAL_SIGINT, () => shutdown(SIGNAL_SIGINT));\n process.once(SIGNAL_SIGTERM, () => shutdown(SIGNAL_SIGTERM));\n } catch (error) {\n if (\n error instanceof InvalidTransportError ||\n error instanceof InvalidBrowserTypeError ||\n error instanceof InvalidLaunchModeError ||\n error instanceof HttpServerStartError\n ) {\n console.error(`Error [${error.code}]: ${error.message}`);\n console.error(`Recovery: ${error.recovery}`);\n process.exit(EXIT_CODE_FAILURE);\n }\n\n const cause = error instanceof Error ? error : new Error(String(error));\n const bootstrapError = new McpServerBootstrapError({ cause });\n console.error(`Error [${bootstrapError.code}]: ${bootstrapError.message}`, bootstrapError.cause);\n console.error(`Recovery: ${bootstrapError.recovery}`);\n process.exit(EXIT_CODE_FAILURE);\n }\n });\n","/**\n * Status Command\n *\n * Shows status of HTTP server and other diagnostics.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\n/**\n * Show status of browse-tool services\n */\nexport const statusCommand = new Command('status')\n .description('Show status of HTTP server and diagnostics')\n .action(async () => {\n try {\n const httpServeDefaults = getCommandConfig<{\n host?: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n }>('httpServe');\n const registryPath =\n process.env.PLAYWRIGHT_REGISTRY_PATH ??\n process.env.PORT_REGISTRY_PATH ??\n httpServeDefaults.registryPath ??\n httpServeDefaults.registryDir;\n\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n }\n\n console.log('browse-tool Status\\n');\n console.log('-'.repeat(50));\n\n // Create container to get HttpServerManager\n const container = createContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n // Get HTTP server status\n const httpStatus = await httpServerManager.getStatus();\n\n if (httpStatus.running) {\n const host = process.env.PLAYWRIGHT_HOST ?? httpServeDefaults.host ?? getPlaywrightHost();\n console.log('HTTP Server: Running');\n console.log(` PID: ${httpStatus.pid}`);\n console.log(` Port: ${httpStatus.port}`);\n console.log(` Health: ${buildPlaywrightBaseUrl(host, httpStatus.port)}/health`);\n if (httpStatus.browserCount !== undefined) {\n console.log(` Browsers: ${httpStatus.browserCount}`);\n }\n } else {\n console.log('HTTP Server: Not Running');\n if (httpStatus.error) {\n console.log(` Error: ${httpStatus.error}`);\n }\n }\n\n console.log('');\n console.log('-'.repeat(50));\n process.exit(0);\n } catch (error) {\n console.error('Error getting status:', error);\n process.exit(1);\n }\n });\n","/**\n * Stop Command\n *\n * Stops HTTP server and cleans up registry/PID files.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\n\n/**\n * Stop HTTP server\n */\nexport const stopCommand = new Command('stop')\n .description('Stop HTTP server and clean up registry/PID files')\n .action(async () => {\n try {\n const httpServeDefaults = getCommandConfig<{\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n }>('httpServe');\n const registryPath =\n process.env.PLAYWRIGHT_REGISTRY_PATH ??\n process.env.PORT_REGISTRY_PATH ??\n httpServeDefaults.registryPath ??\n httpServeDefaults.registryDir;\n\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n }\n\n console.log('Stopping browse-tool services...');\n\n // Create container to get HttpServerManager\n const container = createContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n // Stop HTTP server\n const stopped = await httpServerManager.stop();\n\n if (stopped) {\n console.log('HTTP server stopped');\n console.log('Registry and PID files cleaned up');\n } else {\n console.log('No HTTP server was running');\n }\n\n console.log('Done!');\n process.exit(0);\n } catch (error) {\n console.error('Error stopping services:', error);\n process.exit(1);\n }\n });\n","const ROOT_OPTIONS_WITH_VALUES = new Set(['--config']);\n\nexport function getFirstCommandToken(argv: string[]): string | undefined {\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n\n if (ROOT_OPTIONS_WITH_VALUES.has(arg)) {\n index += 1;\n continue;\n }\n\n if ([...ROOT_OPTIONS_WITH_VALUES].some((option) => arg.startsWith(`${option}=`))) {\n continue;\n }\n\n if (arg.startsWith('-')) {\n continue;\n }\n\n return arg;\n }\n\n return undefined;\n}\n\nexport function shouldRegisterDynamicToolCommands(argv: string[], skipDynamicTools = false): boolean {\n if (skipDynamicTools) {\n return false;\n }\n\n const firstCommand = getFirstCommandToken(argv);\n if (!firstCommand) {\n return false;\n }\n\n if (firstCommand === 'tools') {\n return true;\n }\n\n return firstCommand === 'help' && argv.includes('tools');\n}\n","/**\n * Command Builder Utilities for Tool Commands\n *\n * Converts tool definitions with JSON Schema to Commander.js commands.\n * Handles schema-to-CLI option conversion with proper type handling.\n *\n * DESIGN PATTERNS:\n * - Builder pattern for command construction\n * - Schema-driven command generation\n * - Type-safe option parsing\n *\n * CODING STANDARDS:\n * - Use TypeScript for type safety\n * - Handle all JSON Schema types appropriately\n * - Provide clear CLI conventions (kebab-case)\n *\n * AVOID:\n * - Manual command definitions for each tool\n * - Inconsistent naming conventions\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { Command, Option } from 'commander';\nimport { getCommandConfig, getToolConfig, resolveConfiguredOption } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { type FormatterOptions, type OutputFormat, formatError, formatToolResult } from './outputFormatter.js';\n\n/**\n * JSON Schema property definition\n */\ninterface SchemaProperty {\n type?: string;\n description?: string;\n default?: unknown;\n enum?: string[];\n items?: { type?: string; enum?: string[] };\n oneOf?: SchemaProperty[];\n properties?: Record<string, SchemaProperty>;\n additionalProperties?: boolean | SchemaProperty;\n minimum?: number;\n maximum?: number;\n}\n\n/**\n * Global options inherited by all tool commands\n */\ninterface GlobalOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\nexport interface ToolCommandContext {\n command: Command;\n formatterOptions: FormatterOptions;\n port: number;\n}\n\nexport type ToolCommandExecutor = (\n args: Record<string, unknown>,\n context: ToolCommandContext,\n) => Promise<CallToolResult>;\n\nexport interface ToolCommandDefinition {\n definition: ToolDefinition;\n execute?: ToolCommandExecutor;\n}\n\n/**\n * Convert snake_case to kebab-case for CLI command names\n */\nexport function toKebabCase(str: string): string {\n return str.replace(/_/g, '-');\n}\n\n/**\n * Convert camelCase to kebab-case for CLI option names\n */\nexport function camelToKebab(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\nfunction getSchemaTypes(schema: SchemaProperty): Set<string> {\n const types = new Set<string>();\n\n if (schema.type) {\n types.add(schema.type);\n }\n\n for (const variant of schema.oneOf ?? []) {\n if (variant.type) {\n types.add(variant.type);\n }\n }\n\n return types;\n}\n\n/**\n * Parse a CLI option value based on its JSON Schema type\n */\nexport function parseOptionValue(value: string, schema: SchemaProperty): unknown {\n const types = getSchemaTypes(schema);\n\n if (schema.type === 'number' || schema.type === 'integer') {\n const num = Number(value);\n if (Number.isNaN(num)) {\n throw new Error(`Invalid number: ${value}`);\n }\n return num;\n }\n\n if (schema.type === 'boolean') {\n if (value === 'true' || value === '1') return true;\n if (value === 'false' || value === '0') return false;\n throw new Error(`Invalid boolean: ${value}`);\n }\n\n if (types.has('array') || types.has('object')) {\n try {\n return JSON.parse(value);\n } catch {\n if (schema.type === 'array' || schema.type === 'object') {\n throw new Error(`Invalid JSON: ${value}`);\n }\n }\n }\n\n if (types.has('number') || types.has('integer')) {\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== '') {\n return num;\n }\n }\n\n return value;\n}\n\n/**\n * Build Commander option flag string from property name and schema\n */\nexport function buildOptionFlag(name: string, schema: SchemaProperty): string {\n const kebabName = camelToKebab(name);\n\n if (schema.type === 'boolean') {\n return `--${kebabName}`;\n }\n\n return `--${kebabName} <value>`;\n}\n\n/**\n * Build a Commander command from a tool definition\n */\nexport function buildToolCommand(commandDefinition: ToolCommandDefinition): Command {\n const { definition: tool, execute } = commandDefinition;\n const commandName = toKebabCase(tool.name);\n const command = new Command(commandName);\n\n command.description(tool.description);\n\n const schema = tool.inputSchema;\n const properties = schema.properties || {};\n const required = new Set(schema.required || []);\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const prop = propSchema as SchemaProperty;\n const flag = buildOptionFlag(propName, prop);\n let description = prop.description || '';\n\n if (prop.enum && prop.enum.length > 0) {\n description += ` (choices: ${prop.enum.join(', ')})`;\n }\n\n if (prop.default !== undefined) {\n description += ` (default: ${JSON.stringify(prop.default)})`;\n }\n\n if (required.has(propName)) {\n description += ' [required]';\n }\n\n if (prop.type === 'boolean') {\n if (prop.default === true) {\n command.option(`--no-${camelToKebab(propName)}`, `Disable ${description}`);\n } else {\n command.option(flag, description, prop.default as boolean);\n }\n } else if (prop.enum && prop.enum.length > 0) {\n command.addOption(new Option(flag, description).choices(prop.enum));\n } else {\n command.option(flag, description);\n }\n }\n\n command.action(async function (this: Command) {\n const options = this.optsWithGlobals() as Record<string, unknown> & GlobalOptions;\n const commandDefaults = getCommandConfig<Partial<GlobalOptions>>('tools');\n const format = resolveConfiguredOption(\n this,\n 'format',\n (options.format ?? 'json') as OutputFormat,\n commandDefaults.format,\n );\n const color = resolveConfiguredOption(this, 'color', options.color ?? true, commandDefaults.color);\n const portValue = resolveConfiguredOption(\n this,\n 'port',\n String(options.port ?? DEFAULT_MCP_PORT),\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n const formatterOptions: FormatterOptions = { format, color };\n const toolDefaults = getToolConfig(tool.name);\n\n try {\n const args: Record<string, unknown> = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const prop = propSchema as SchemaProperty;\n const kebabName = camelToKebab(propName);\n const optionKey = kebabName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());\n const optionSource = this.getOptionValueSourceWithGlobals(optionKey);\n let value = options[optionKey];\n\n if (prop.type === 'boolean' && prop.default === true) {\n value = options[optionKey];\n }\n\n if (\n (optionSource === undefined || optionSource === 'default' || optionSource === 'implied') &&\n toolDefaults[propName] !== undefined\n ) {\n value = toolDefaults[propName];\n }\n\n if (value !== undefined) {\n if (typeof value === 'string' && prop.type !== 'string') {\n args[propName] = parseOptionValue(value, prop);\n } else {\n args[propName] = value;\n }\n }\n }\n\n for (const reqField of required) {\n if (args[reqField] === undefined) {\n const kebabField = camelToKebab(reqField);\n console.error(formatError(`Missing required option: --${kebabField}`, formatterOptions.color));\n process.exit(1);\n }\n }\n\n const port = Number.parseInt(portValue, 10);\n const client = createToolClient({ port });\n const result = execute\n ? await execute(args, { command: this, formatterOptions, port })\n : await client.execute(tool.name, args);\n const output = formatToolResult(result, formatterOptions);\n\n if (output) {\n console.log(output);\n }\n\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n\n return command;\n}\n\n/**\n * Build commands for all tools from definitions\n */\nexport function buildAllToolCommands(tools: ToolCommandDefinition[]): Command[] {\n return tools.map(buildToolCommand);\n}\n","/**\n * Tool Commands Registration Utilities\n *\n * Dynamically creates CLI commands from tool definitions fetched from the HTTP server.\n * Adds CLI-only shims for MCP capabilities that are not directly exposed as HTTP tools.\n *\n * DESIGN PATTERNS:\n * - Factory pattern for command creation\n * - Pure functions for data transformation\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - Hardcoded tool definitions\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { type ToolCommandDefinition, buildAllToolCommands, toKebabCase } from './commandBuilder.js';\nimport type { BrowserInfo } from './httpClient.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { formatError } from './outputFormatter.js';\n\nconst SESSION_TOOL_NAME = 'browser_list_session';\nconst SESSION_TOOL_DESCRIPTION = 'List browsers and pages currently tracked by the browse-tool HTTP server.';\n\n/**\n * Tool category groupings for help organization\n */\nconst TOOL_CATEGORIES: Record<string, string[]> = {\n browser: [\n 'browser_launch',\n 'browser_close',\n 'browser_start_recording',\n 'browser_stop_recording',\n 'browser_list_pages',\n 'browser_resize_page',\n SESSION_TOOL_NAME,\n ],\n navigation: ['browser_navigate', 'browser_go_back', 'browser_go_forward', 'browser_reload', 'browser_wait_for'],\n interaction: [\n 'browser_click',\n 'browser_type',\n 'browser_fill',\n 'browser_select',\n 'browser_hover',\n 'browser_press_key',\n 'browser_drag',\n 'browser_upload_file',\n ],\n extraction: ['browser_snapshot', 'browser_screenshot', 'browser_pdf'],\n script: ['browser_evaluate_script', 'browser_run_code', 'browser_list_snippets'],\n network: ['browser_list_network_requests', 'browser_get_network_request'],\n dialog: ['browser_handle_dialog'],\n tracing: ['browser_start_trace', 'browser_stop_trace'],\n testing: ['browser_expect', 'run_spec', 'discover_specs'],\n profile: ['browser_list_profiles', 'browser_delete_profile'],\n};\n\nconst SESSION_TOOL_DEFINITION: ToolDefinition = {\n name: SESSION_TOOL_NAME,\n description: SESSION_TOOL_DESCRIPTION,\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n};\n\n/**\n * Get category for a tool name\n */\nexport function getToolCategory(toolName: string): string {\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n if (tools.includes(toolName)) {\n return category;\n }\n }\n return 'other';\n}\n\n/**\n * Create a parent command for tool commands with global options\n */\nexport function createToolsCommand(): Command {\n return new Command('tools')\n .description('Execute browser automation tools')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT));\n}\n\nfunction formatSessionResult(browsers: BrowserInfo[]) {\n return {\n browserCount: browsers.length,\n browsers: browsers.map((browser) => ({\n browserId: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n pageIds: browser.pageIds,\n createdAt: browser.createdAt,\n })),\n };\n}\n\nfunction createSessionToolCommandDefinition(): ToolCommandDefinition {\n return {\n definition: SESSION_TOOL_DEFINITION,\n execute: async (_args, context) => {\n const client = createToolClient({ port: context.port });\n const browsers = await client.listBrowsers();\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(formatSessionResult(browsers), null, 2),\n },\n ],\n };\n },\n };\n}\n\nfunction buildCommandDefinitions(tools: ToolDefinition[]): ToolCommandDefinition[] {\n return [...tools.map((definition) => ({ definition })), createSessionToolCommandDefinition()];\n}\n\n/**\n * Register tool commands under a parent command\n * Fetches tool definitions from the HTTP server and creates corresponding CLI commands\n */\nexport async function registerToolCommands(parentCommand: Command, options?: { port?: number }): Promise<void> {\n const commandDefaults = getCommandConfig<{ port?: number }>('tools');\n const port = Number(options?.port ?? process.env.PLAYWRIGHT_PORT ?? commandDefaults.port ?? DEFAULT_MCP_PORT);\n\n try {\n const client = createToolClient({ port });\n const tools = await client.listTools();\n const toolCommands = buildAllToolCommands(buildCommandDefinitions(tools));\n\n for (const cmd of toolCommands) {\n parentCommand.addCommand(cmd);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`${formatError(`Failed to load tool commands: ${errorMessage}`, true)}\\n`);\n process.stderr.write('Make sure the HTTP server is running: browse-tool http-serve\\n');\n }\n}\n\n/**\n * Create tool commands from static definitions (for offline use)\n */\nexport function createToolCommandsFromDefinitions(tools: ToolDefinition[]): Command[] {\n return buildAllToolCommands(buildCommandDefinitions(tools));\n}\n\n/**\n * Get help text for tool categories\n */\nexport function getCategoryHelp(): string {\n const lines: string[] = ['Tool Categories:'];\n\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n const kebabTools = tools.map(toKebabCase).join(', ');\n lines.push(` ${category}: ${kebabTools}`);\n }\n\n return lines.join('\\n');\n}\n","#!/usr/bin/env node\nimport 'reflect-metadata/lite';\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { chromeServeCommand } from './commands/chrome-serve.js';\nimport { execCustomToolCommand, listCustomToolsCommand } from './commands/custom-tools.js';\nimport { dockerBuildCftCommand } from './commands/docker-build-cft.js';\nimport { execCommand } from './commands/exec.js';\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { statusCommand } from './commands/status.js';\nimport { stopCommand } from './commands/stop.js';\nimport { initializeCliRuntime } from './config.js';\nimport { shouldRegisterDynamicToolCommands } from './utils/cliArgs.js';\nimport { DEFAULT_MCP_PORT } from './utils/networkConfig.js';\nimport { createToolsCommand, registerToolCommands } from './utils/toolCommands.js';\n\nconst SKIP_DYNAMIC_TOOLS_ENV = 'PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS';\n\nfunction resolveDynamicToolsPort(argv: string[], fallbackPort: string | number): number {\n const args = [...argv];\n\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index];\n\n if (arg === '--port' || arg === '-p') {\n const candidate = args[index + 1];\n const parsed = Number(candidate);\n if (!Number.isNaN(parsed) && candidate) {\n return parsed;\n }\n continue;\n }\n\n if (arg.startsWith('--port=')) {\n const parsed = Number(arg.slice('--port='.length));\n if (!Number.isNaN(parsed)) {\n return parsed;\n }\n continue;\n }\n\n if (arg.startsWith('-p=')) {\n const parsed = Number(arg.slice('-p='.length));\n if (!Number.isNaN(parsed)) {\n return parsed;\n }\n }\n }\n\n return Number(fallbackPort);\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n const runtime = initializeCliRuntime(process.argv.slice(2));\n const configuredToolsPort =\n process.env.PLAYWRIGHT_PORT ??\n runtime.config.commands.tools?.port ??\n runtime.config.commands.exec?.port ??\n DEFAULT_MCP_PORT;\n const program = new Command();\n\n program\n .name('browse-tool')\n .description(\n 'MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support',\n )\n .option('--config <path>', 'Path to browse-tool config file or config directory')\n .version(packageJson.version);\n\n // Add all commands\n program.addCommand(mcpServeCommand);\n program.addCommand(httpServeCommand);\n program.addCommand(chromeServeCommand);\n program.addCommand(dockerBuildCftCommand);\n program.addCommand(stopCommand);\n program.addCommand(statusCommand);\n program.addCommand(execCommand);\n program.addCommand(listCustomToolsCommand);\n program.addCommand(execCustomToolCommand);\n\n // Create and register tool commands (dynamically generated from HTTP server)\n const toolsCommand = createToolsCommand();\n if (shouldRegisterDynamicToolCommands(process.argv.slice(2), process.env[SKIP_DYNAMIC_TOOLS_ENV] === '1')) {\n await registerToolCommands(toolsCommand, {\n port: resolveDynamicToolsPort(process.argv.slice(2), configuredToolsPort),\n });\n }\n program.addCommand(toolsCommand);\n\n // Parse arguments\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}
|