search-mcp-server
v0.14.4MCP server for browser automation via Jan Browser extension - provides tools for web navigation, interaction, and search
39
Total
6
Critical
18
High
15
Medium
Findings
unknownEnvironment file access
Detected by automated pattern matching (rule DE-002) with medium confidence. May be a false positive.
790: }
791: let bridgeHost = process.env.BRIDGE_HOST || "127.0.0.1";
>>> 792: let bridgePort = Number(process.env.BRIDGE_PORT || 17389);
793: let bridgeToken = process.env.BRIDGE_TOKEN || void 0;
794: const SERVER_VERSION = "0.13.2";Report false positiveEnvironment file access
Detected by automated pattern matching (rule DE-002) with medium confidence. May be a false positive.
789: ];
790: }
>>> 791: let bridgeHost = process.env.BRIDGE_HOST || "127.0.0.1";
792: let bridgePort = Number(process.env.BRIDGE_PORT || 17389);
793: let bridgeToken = process.env.BRIDGE_TOKEN || void 0;Report false positiveEnvironment file access
Detected by automated pattern matching (rule DE-002) with medium confidence. May be a false positive.
9: import { z } from "zod";
10: import { zodToJsonSchema } from "zod-to-json-schema";
>>> 11: const LOG_FILE$1 = process.env.MCP_LOG_FILE;
12: const MAX_BRIDGE_RETRIES = 10;
13: function logToFile$1(message) {Report false positiveEnvironment file access
Detected by automated pattern matching (rule DE-002) with medium confidence. May be a false positive.
>>> 1: {"version":3,"file":"index.js","sources":["../src/connection/bridge.ts","../../mcp-shared/src/tools/schemas.ts","../../mcp-shared/src/tools/handlers.ts","../../mcp-shared/src/utils/sanitize.ts","../../mcp-shared/src/tools/automation.ts","../../mcp-shared/src/tools/navigation.ts","../../mcp-shared/src/tools/observation.ts","../../mcp-shared/src/tools/tabs.ts","../../mcp-shared/src/tools/index.ts","../src/index.ts"],"sourcesContent":["/**\n * WebSocket bridge utilities for communicating with the browser extension\n */\nimport { WebSocket } from \"ws\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { appendFileSync } from \"fs\";\n\nconst LOG_FILE = process.env.MCP_LOG_FILE;\nconst MAX_BRIDGE_RETRIES = 10;\n\nfunction logToFile(message: string) {\n if (LOG_FILE) {\n try {\n appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${message}\\n`);\n } catch (e) {}\n }\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n const stack = error.stack ? `\\n${error.stack}` : \"\";\n return `${error.name}: ${error.message}${stack}`;\n }\n if (typeof error === \"string\") {\n return error;\n }\n try {\n return JSON.stringify(error);\n } catch (e) {\n return String(error);\n }\n}\n\nfunction logError(message: string, error?: unknown) {\n if (error !== undefined) {\n logToFile(`ERROR: ${message}\\n${formatError(error)}`);\n } else {\n logToFile(`ERROR: ${message}`);\n }\n}\n\nconst pendingCalls = new Map<string, { resolve: (val: any) => void; reject: (err: any) => void }>();\nlet activeTabId: number | null = null;\n\ninterface ProfileConnection {\n id: string;\n label: string;\n socket: WebSocket;\n ready: boolean;\n lastPongTime: number;\n pingInterval: NodeJS.Timeout | null;\n pongTimeout: NodeJS.Timeout | null;\n}\n\nconst connections = new Map<string, ProfileConnection>();\nlet activeProfileId: string | null = null;\nlet shuttingDown = false;\n\n// Ping/Pong heartbeat configuration\nconst PING_INTERVAL_MS = 15000; // Send ping every 15 seconds\nconst PONG_TIMEOUT_MS = 5000; // Expect pong within 5 seconds\n\n// Exponential backoff retry configuration\nconst INITIAL_RETRY_DELAY_MS = 1000; // Start with 1s delay\nconst MAX_RETRY_DELAY_MS = 10000; // Cap at 10s delay\n\nexport function setActiveTabId(tabId: number | null) {\n activeTabId = tabId;\n logToFile(`Active tab set to: ${tabId}`);\n}\n\nexport function getActiveTabId(): number | null {\n return activeTabId;\n}\n\nexport function hasActiveTab(): boolean {\n return activeTabId !== null;\n}\n\nfunction stopHeartbeat(connection: ProfileConnection) {\n if (connection.pingInterval) {\n clearInterval(connection.pingInterval);\n connection.pingInterval = null;\n }\n if (connection.pongTimeout) {\n clearTimeout(connection.pongTimeout);\n connection.pongTimeout = null;\n }\n logToFile(`Heartbeat: Stopped for profile ${connection.id}`);\n}\n\nfunction closeConnection(connection: ProfileConnection, code = 1001, reason = \"Shutting down\") {\n stopHeartbeat(connection);\n try {\n if (\n connection.socket.readyState === WebSocket.OPEN ||\n connection.socket.readyState === WebSocket.CONNECTING\n ) {\n connection.socket.close(code, reason);\n }\n } catch (error) {\n logError(`Error closing socket for profile ${connection.id}`, error);\n }\n}\n\nfunction startHeartbeat(profileId: string) {\n const connection = connections.get(profileId);\n if (!connection) return;\n\n stopHeartbeat(connection);\n connection.lastPongTime = Date.now();\n\n connection.pingInterval = setInterval(() => {\n if (!connections.has(profileId)) {\n stopHeartbeat(connection);\n return;\n }\n\n if (connection.socket.readyState !== WebSocket.OPEN) {\n logToFile(`Heartbeat: Socket not open for profile ${profileId}, stopping`);\n stopHeartbeat(connection);\n return;\n }\n\n const timeSinceLastPong = Date.now() - connection.lastPongTime;\n if (timeSinceLastPong > PING_INTERVAL_MS + PONG_TIMEOUT_MS) {\n logToFile(\n `Heartbeat: No pong received for ${timeSinceLastPong}ms on profile ${profileId}, connection may be stale`,\n );\n try {\n connection.socket.close();\n } catch (e) {\n logToFile(`Heartbeat: Error closing stale socket for profile ${profileId}: ${e}`);\n }\n stopHeartbeat(connection);\n return;\n }\n\n try {\n logToFile(`Heartbeat: Sending ping to profile ${profileId}`);\n connection.socket.send(JSON.stringify({ kind: \"ping\" }));\n\n if (connection.pongTimeout) clearTimeout(connection.pongTimeout);\n connection.pongTimeout = setTimeout(() => {\n logToFile(`Heartbeat: Pong timeout for profile ${profileId}, connection may be unhealthy`);\n }, PONG_TIMEOUT_MS);\n } catch (e) {\n logToFile(`Heartbeat: Error sending ping to profile ${profileId}: ${e}`);\n stopHeartbeat(connection);\n }\n }, PING_INTERVAL_MS);\n\n logToFile(`Heartbeat: Started for profile ${profileId}`);\n}\n\nfunction handlePong(profileId: string) {\n const connection = connections.get(profileId);\n if (!connection) return;\n\n connection.lastPongTime = Date.now();\n if (connection.pongTimeout) {\n clearTimeout(connection.pongTimeout);\n connection.pongTimeout = null;\n }\n logToFile(`Heartbeat: Received pong from profile ${profileId}`);\n}\n\nfunction normalizeError(error: unknown): Error {\n if (error instanceof Error) {\n return error;\n }\n return new Error(typeof error === \"string\" ? error : String(error));\n}\n\nfunction isRetriableBridgeError(error: Error): boolean {\n const message = error.message || \"\";\n return (\n message.includes(\"not connected\") ||\n message.includes(\"disconnected\") ||\n message.includes(\"WebSocket is not open\") ||\n message.includes(\"closed\")\n );\n}\n\nfunction getActiveConnection(): ProfileConnection | null {\n if (activeProfileId) {\n const connection = connections.get(activeProfileId);\n if (connection && connection.ready && connection.socket.readyState === WebSocket.OPEN) {\n return connection;\n }\n }\n\n for (const connection of connections.values()) {\n if (connection.ready && connection.socket.readyState === WebSocket.OPEN) {\n activeProfileId = connection.id;\n return connection;\n }\n }\n\n return null;\n}\n\nexport function getProfileState() {\n return {\n activeProfileId,\n profileCount: connections.size,\n profiles: Array.from(connections.values()).map((connection) => ({\n id: connection.id,\n label: connection.label,\n ready: connection.ready,\n })),\n };\n}\n\nfunction broadcastProfileState() {\n const snapshot = getProfileState();\n const payload = JSON.stringify({\n kind: \"profile_state\",\n activeProfileId: snapshot.activeProfileId,\n profileCount: snapshot.profileCount,\n profiles: snapshot.profiles,\n });\n\n for (const connection of connections.values()) {\n if (connection.socket.readyState === WebSocket.OPEN) {\n try {\n connection.socket.send(payload);\n } catch (error) {\n logError(`Failed to broadcast profile state to profile ${connection.id}`, error);\n }\n }\n }\n}\n\nfunction setActiveProfile(profileId: string | null) {\n if (profileId && !connections.has(profileId)) {\n logToFile(`Attempted to activate unknown profile: ${profileId}`);\n return;\n }\n\n if (activeProfileId === profileId) {\n return;\n }\n\n activeProfileId = profileId;\n cleanupPendingCalls();\n logToFile(`Active profile set to: ${profileId ?? \"none\"}`);\n broadcastProfileState();\n}\n\nexport function registerExtensionConnection(profileId: string, label: string | null, socket: WebSocket) {\n if (shuttingDown) {\n try {\n socket.close(1012, \"Server restarting\");\n } catch {}\n return;\n }\n\n const normalizedLabel = label?.trim() || `Profile ${profileId.slice(0, 8)}`;\n\n if (connections.has(profileId)) {\n try {\n connections.get(profileId)?.socket?.close();\n } catch (error) {\n logError(`Error closing existing socket for profile ${profileId}`, error);\n }\n }\n\n const connection: ProfileConnection = {\n id: profileId,\n label: normalizedLabel,\n socket,\n ready: false,\n lastPongTime: Date.now(),\n pingInterval: null,\n pongTimeout: null,\n };\n\n connections.set(profileId, connection);\n if (!activeProfileId) {\n activeProfileId = profileId;\n }\n\n startHeartbeat(profileId);\n broadcastProfileState();\n}\n\nexport function removeExtensionConnection(profileId: string) {\n const connection = connections.get(profileId);\n if (!connection) return;\n\n closeConnection(connection, 1001, \"Extension disconnected\");\n connections.delete(profileId);\n\n if (activeProfileId === profileId) {\n const firstProfile = connections.values().next();\n setActiveProfile(firstProfile.done ? null : firstProfile.value.id);\n } else {\n broadcastProfileState();\n }\n}\n\nexport function closeAllConnections() {\n shuttingDown = true;\n for (const connection of connections.values()) {\n closeConnection(connection, 1012, \"Server shutting down\");\n }\n connections.clear();\n activeProfileId = null;\n cleanupPendingCalls();\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function hasExtensionConnection(): boolean {\n const connection = getActiveConnection();\n return connection !== null && connection.socket.readyState === WebSocket.OPEN && connection.ready;\n}\n\n/**\n * Wait for the browser extension to connect to the bridge\n */\nexport async function waitForBridgeConnection(timeoutMs: number = 4000): Promise<void> {\n return new Promise((resolve, reject) => {\n const startTime = Date.now();\n const checkInterval = setInterval(() => {\n if (hasExtensionConnection()) {\n clearInterval(checkInterval);\n resolve();\n } else if (Date.now() - startTime > timeoutMs) {\n clearInterval(checkInterval);\n reject(new Error(\"Browser extension not connected to bridge\"));\n }\n }, 100);\n });\n}\n\n/**\n * Call a tool on the browser extension via WebSocket bridge with retry logic and exponential backoff\n */\nexport async function callExtension(tool: string, params: any): Promise<any> {\n let attempts = 0;\n let lastError: Error | null = null;\n\n while (attempts < MAX_BRIDGE_RETRIES) {\n if (!hasExtensionConnection()) {\n attempts++;\n lastError = new Error(\"Browser extension not connected to bridge\");\n\n const delayMs = Math.min(\n INITIAL_RETRY_DELAY_MS * Math.pow(2, attempts - 1),\n MAX_RETRY_DELAY_MS,\n );\n logToFile(`Retry ${attempts}/${MAX_BRIDGE_RETRIES}: Bridge not connected, waiting ${delayMs}ms...`);\n await delay(delayMs);\n continue;\n }\n\n try {\n return await sendToolCall(tool, params);\n } catch (error) {\n const normalized = normalizeError(error);\n lastError = normalized;\n\n if (!isRetriableBridgeError(normalized)) {\n logError(\n `Bridge call failed without retry for tool \"${tool}\"`,\n normalized,\n );\n throw normalized;\n }\n\n attempts++;\n\n const delayMs = Math.min(\n INITIAL_RETRY_DELAY_MS * Math.pow(2, attempts - 1),\n MAX_RETRY_DELAY_MS,\n );\n logToFile(`Retry ${attempts}/${MAX_BRIDGE_RETRIES} for ${tool} after ${delayMs}ms (error: ${normalized.message})`);\n await delay(delayMs);\n }\n }\n\n const finalError =\n lastError || new Error(\"Browser extension not connected to bridge\");\n logError(\n `Failed to call extension tool \"${tool}\" after ${attempts} attempts`,\n finalError,\n );\n throw finalError;\n}\n\nfunction sendToolCall(tool: string, params: any): Promise<any> {\n return new Promise((resolve, reject) => {\n const connection = getActiveConnection();\n if (!connection) {\n reject(new Error(\"Browser extension not connected to bridge\"));\n return;\n }\n\n const callId = uuidv4();\n const timeoutMs = tool === \"screenshot\" ? 10000 : 60000; // Increased from 30s to 60s\n const timeout = setTimeout(() => {\n pendingCalls.delete(callId);\n const msg = `Tool call timeout after ${timeoutMs}ms: ${tool}`;\n logToFile(msg);\n reject(new Error(msg));\n }, timeoutMs);\n\n pendingCalls.set(callId, {\n resolve: (val) => {\n clearTimeout(timeout);\n logToFile(`Tool call resolved: ${tool} (${callId}) [profile ${connection.id}]`);\n resolve(val);\n },\n reject: (err) => {\n clearTimeout(timeout);\n logToFile(`Tool call rejected: ${tool} (${callId}) [profile ${connection.id}] - ${err}`);\n reject(err);\n },\n });\n\n const message = {\n kind: \"call\",\n id: callId,\n tool: tool,\n params: params,\n };\n\n logToFile(`Sending to extension: ${tool} (${callId}) [profile ${connection.id}]`);\n\n try {\n connection.socket.send(JSON.stringify(message));\n } catch (error) {\n clearTimeout(timeout);\n pendingCalls.delete(callId);\n const normalized = normalizeError(error);\n logError(\"Failed to send message to extension\", normalized);\n reject(normalized);\n }\n });\n}\n\n/**\n * Handle incoming message from browser extension\n * Extension sends: {id, kind: \"result\", ok, data?, error?} or {kind: \"pong\"} or {kind: \"ready\"}\n */\nexport function handleExtensionMessage(profileId: string, data: any) {\n try {\n let msg: any;\n if (data && data.type === \"Buffer\" && Array.isArray(data.data)) {\n const buffer = Buffer.from(data.data);\n msg = JSON.parse(buffer.toString());\n } else if (typeof data === \"string\") {\n msg = JSON.parse(data);\n } else if (Buffer.isBuffer(data)) {\n msg = JSON.parse(data.toString());\n } else {\n msg = data;\n }\n logToFile(`Received from extension (${profileId}): ${JSON.stringify(msg)}`);\n\n if (msg.kind === \"pong\") {\n handlePong(profileId);\n return;\n }\n\n if (msg.kind === \"ready\") {\n const connection = connections.get(profileId);\n if (connection) {\n connection.ready = true;\n connection.lastPongTime = Date.now();\n if (!activeProfileId) {\n activeProfileId = profileId;\n }\n }\n broadcastProfileState();\n logToFile(`Extension handshake complete for profile ${profileId} - bridge is ready`);\n return;\n }\n\n if (msg.kind === \"activate_profile\") {\n const requestedProfileId = typeof msg.profileId === \"string\" ? msg.profileId : profileId;\n setActiveProfile(requestedProfileId);\n return;\n }\n\n if (msg.id && pendingCalls.has(msg.id)) {\n const { resolve, reject } = pendingCalls.get(msg.id)!;\n pendingCalls.delete(msg.id);\n\n if (msg.kind === \"result\") {\n if (msg.ok) {\n logToFile(`Extension call succeeded: ${msg.id}`);\n resolve(msg);\n } else {\n logError(\"Extension call returned failure\", msg.error);\n reject(new Error(msg.error || \"Extension call failed\"));\n }\n } else {\n if (msg.error) {\n reject(new Error(msg.error.message || String(msg.error)));\n } else {\n resolve(msg.result || msg);\n }\n }\n }\n } catch (err) {\n logError(\"Error handling extension message\", err);\n }\n}\n\n/**\n * Clean up pending calls when extension disconnects\n */\nexport function cleanupPendingCalls() {\n for (const [id, { reject }] of pendingCalls.entries()) {\n reject(new Error(\"Browser extension disconnected\"));\n }\n pendingCalls.clear();\n}\n","/**\n * Shared Zod schemas for browser automation tools\n * Used by both mcp-server and mcp-web-client\n */\nimport { z } from 'zod';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport type { ToolSchema } from '../types/index.js';\n\n// Common schemas\nconst TargetSchema = z\n .string()\n .min(1)\n .describe(\n 'Element target: snapshot ref such as \"s1e1\" for main frame or \"s1f2e5\" for iframe elements (coordinates are also accepted).'\n );\n\n// Tool Zod schemas\nexport const ClickSchema = z.object({\n target: TargetSchema,\n});\n\nexport const TypeSchema = z\n .object({\n target: TargetSchema,\n text: z\n .string()\n .optional()\n .describe(\n 'Text to type. Embed key presses with <kbd>…</kbd>, e.g., \"Hello <kbd>Enter</kbd>\". Supported keys: Meta (platform-aware: Cmd on macOS, Ctrl on Windows/Linux), Alt, Shift, Enter, Tab, Escape, Backspace, Delete, Space, ArrowLeft, ArrowRight, ArrowUp, ArrowDown, Home, End, PageUp, PageDown, and single characters. Combine with +, e.g., \"<kbd>Meta+A</kbd>\" for select-all on any platform.'\n ),\n clear: z\n .boolean()\n .optional()\n .describe('Whether to clear the existing value before typing. Default: true'),\n submit: z\n .boolean()\n .optional()\n .describe('Convenience flag to press Enter after typing'),\n })\n .superRefine((value, ctx) => {\n const rawText = typeof value.text === 'string' ? value.text : '';\n const strippedText = rawText.replace(/<kbd>.*?<\\/kbd>/gi, '').trim();\n const hasText = strippedText.length > 0;\n const hasKeyTokens = /<kbd>.*?<\\/kbd>/i.test(rawText);\n\n if (!hasText && !hasKeyTokens && value.submit !== true) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'Provide text to type or embed key presses in <kbd>…</kbd>',\n });\n }\n });\n\nexport const DragSchema = z.object({\n start: TargetSchema.describe(\n 'Drag starting point (snapshot ref such as \"s1e5\" for main frame or \"s1f2e10\" for iframe elements; coordinates accepted).'\n ),\n end: TargetSchema.describe(\n 'Drop target (snapshot ref such as \"s1e5\" for main frame or \"s1f2e10\" for iframe elements; coordinates accepted).'\n ),\n});\n\nexport const NavigateSchema = z.object({\n target: z\n .string()\n .describe(\n 'Where to go: full URL, bare domain (we add https://), \"back\"/\"forward\" for history navigation, or \"new\" to create a new tab'\n ),\n forceNewTabOpen: z\n .boolean()\n .optional()\n .describe(\n 'If true, forces creating a new tab in the automation group even if a tab is already assigned. Default: false (reuses existing tab)'\n ),\n});\n\nexport const ScrollSchema = z.object({\n direction: z\n .enum(['up', 'down', 'top', 'bottom'])\n .describe('Scroll direction/position for the current page or element'),\n amount: z\n .number()\n .optional()\n .describe('Pixels to scroll for up/down (default 500). Ignored for top/bottom.'),\n target: z\n .string()\n .optional()\n .describe(\n \"Optional snapshot ref (e.g., 's1e5' for main frame or 's1f2e10' for iframe elements) to scroll a specific element instead of the page\"\n ),\n});\n\nexport const SnapshotSchema = z.object({\n fullPage: z\n .boolean()\n .optional()\n .describe(\n 'Capture full page (true) or only viewport-visible content (false). Default: true'\n ),\n detail: z\n .enum(['shorten', 'all'])\n .optional()\n .describe(\n 'Snapshot detail level. Default: shorten. Use \"all\" for no limits. Result includes snapshot text, plain screenshot, and screenshot with refs using same snapshotId.'\n ),\n});\n\nexport const TabsSchema = z.object({});\n\nexport const TabSwitchSchema = z.object({\n tabId: z.number().describe('The tab ID to switch to (from browser_tabs output)'),\n});\n\nexport const CloseTabsSchema = z.object({});\n\n// Tool definitions with schemas\nexport const TOOL_DEFINITIONS = {\n browser_click: {\n name: 'browser_click',\n description:\n \"Click an element by snapshot ref (e.g., 's1e5' for main frame or 's1f2e10' for iframe elements). Returns: (1) shortened YAML snapshot text, (2) base64 PNG screenshot, (3) base64 PNG screenshot with refs. Note: Snapshot is shortened by default. If you see an element in the screenshot that has no ref in the snapshot, call browser_snapshot with detail='all' to get the full snapshot. Warning: Snapshot may not reflect latest page state due to async web content; call browser_snapshot to get current state if needed.\",\n schema: ClickSchema,\n },\n browser_type: {\n name: 'browser_type',\n description:\n \"Focus an element by snapshot ref (e.g., 's1e5' for main frame or 's1f2e10' for iframe elements), type text/keys. Returns: (1) shortened YAML snapshot text, (2) base64 PNG screenshot, (3) base64 PNG screenshot with refs. Note: Snapshot is shortened by default. If you see an element in the screenshot that has no ref in the snapshot, call browser_snapshot with detail='all' to get the full snapshot. Warning: Snapshot may not reflect latest page state due to async web content; call browser_snapshot to get current state if needed.\",\n schema: TypeSchema,\n },\n browser_drag: {\n name: 'browser_drag',\n description:\n \"Drag from start to end targets identified by snapshot refs (e.g., 's1e5' main frame or 's1f2e10' iframe). Returns: (1) shortened YAML snapshot text, (2) base64 PNG screenshot, (3) base64 PNG screenshot with refs. Note: Snapshot is shortened by default. If you see an element in the screenshot that has no ref in the snapshot, call browser_snapshot with detail='all' to get the full snapshot. Warning: Snapshot may not reflect latest page state due to async web content; call browser_snapshot to get current state if needed.\",\n schema: DragSchema,\n },\n browser_navigate: {\n name: 'browser_navigate',\n description:\n \"Navigate the browser tab: open a URL, move browser history, or create a new tab. Pass 'target' as a URL/domain (https added if missing), 'back'/'forward' for history, or 'new' to create a blank tab. Set 'forceNewTabOpen: true' to force creating a new tab when navigating (instead of reusing existing assigned tab). Returns: (1) shortened YAML snapshot text, (2) base64 PNG screenshot, (3) base64 PNG screenshot with refs. Note: Snapshot is shortened by default. If you see an element in the screenshot that has no ref in the snapshot, call browser_snapshot with detail='all' to get the full snapshot. Warning: Snapshot may not reflect latest page state due to async web content; call browser_snapshot to get current state if needed.\",\n schema: NavigateSchema,\n },\n browser_scroll: {\n name: 'browser_scroll',\n description:\n \"Scroll the current page or a referenced element; directions: up/down/top/bottom. amount controls pixel distance for up/down. Returns: (1) shortened YAML snapshot text, (2) base64 PNG screenshot, (3) base64 PNG screenshot with refs. Note: Snapshot is shortened by default. If you see an element in the screenshot that has no ref in the snapshot, call browser_snapshot with detail='all' to get the full snapshot. Warning: Snapshot may not reflect latest page state due to async web content; call browser_snapshot to get current state if needed.\",\n schema: ScrollSchema,\n },\n browser_snapshot: {\n name: 'browser_snapshot',\n description:\n \"Capture an accessibility snapshot of the current tab (ARIA tree, landmarks, headings, ref map). Returns: (1) YAML snapshot text (with ref map), (2) base64 PNG screenshot, (3) base64 PNG screenshot with refs overlaid. Default detail='shorten' for optimized output. Use detail='all' for complete snapshot when you see elements in the screenshot without refs in the snapshot. Use fullPage=false for viewport-only.\",\n schema: SnapshotSchema,\n },\n browser_tabs: {\n name: 'browser_tabs',\n description:\n \"Get all tabs in the Jan Browser MCP tab group with their metadata (tabId, url, title, active status). Use this to see available tabs for browser automation. If no tab group exists but a tab is assigned for browser use, creates the tab group and returns info. Returns error message if no tab group and no assigned tab.\",\n schema: TabsSchema,\n },\n browser_tab_switch: {\n name: 'browser_tab_switch',\n description:\n \"Switch the current browser automation to a different tab by its ID. Get available tab IDs from browser_tabs tool. The switched tab becomes the active tab for subsequent browser operations. Returns: (1) shortened YAML snapshot text, (2) base64 PNG screenshot, (3) base64 PNG screenshot with refs of the switched tab. Warning: Snapshot may not reflect latest page state due to async web content; call browser_snapshot to get current state if needed.\",\n schema: TabSwitchSchema,\n },\n browser_close_tabs: {\n name: 'browser_close_tabs',\n description:\n \"Close all tabs in the Jan Browser MCP tab group and ungroup them. This clears the automation session. Use browser_navigate to create a new tab afterwards if needed.\",\n schema: CloseTabsSchema,\n },\n} as const;\n\n/**\n * Get tool schema with JSON Schema inputSchema\n */\nexport function getToolSchema(\n name: keyof typeof TOOL_DEFINITIONS\n): ToolSchema {\n const def = TOOL_DEFINITIONS[name];\n return {\n name: def.name,\n description: def.description,\n inputSchema: zodToJsonSchema(def.schema) as Record<string, unknown>,\n };\n}\n\n/**\n * Get all exposed tool schemas (excludes disabled tools like browser_drag)\n */\nexport function getToolSchemas(): ToolSchema[] {\n return [\n getToolSchema('browser_click'),\n getToolSchema('browser_type'),\n getToolSchema('browser_navigate'),\n getToolSchema('browser_scroll'),\n getToolSchema('browser_snapshot'),\n getToolSchema('browser_tabs'),\n getToolSchema('browser_tab_switch'),\n getToolSchema('browser_close_tabs'),\n ];\n}\n\n/**\n * Get all tool schemas including disabled ones\n */\nexport function getAllToolSchemas(): ToolSchema[] {\n return Object.keys(TOOL_DEFINITIONS).map((name) =>\n getToolSchema(name as keyof typeof TOOL_DEFINITIONS)\n );\n}\n\n// Pre-computed for convenience (lazy evaluation)\nlet _toolSchemas: ToolSchema[] | null = null;\nexport function TOOL_SCHEMAS(): ToolSchema[] {\n if (!_toolSchemas) {\n _toolSchemas = getToolSchemas();\n }\n return _toolSchemas;\n}\n","/**\n * Shared result handler for normalizing tool responses\n * Ensures both mcp-server and mcp-web-client return identical ToolResult format\n */\nimport type { ToolResult, ToolResultContent } from '../types/index.js';\n\n/**\n * Raw response from extension (via WebSocket bridge or chrome.runtime)\n */\nexport interface ExtensionResponse {\n kind?: 'result';\n id?: string;\n ok?: boolean;\n content?: ToolResultContent[];\n _meta?: {\n urls?: string[];\n tabId?: number | null;\n };\n data?: unknown;\n error?: string;\n isError?: boolean;\n}\n\n/**\n * Extract ToolResult from extension response\n * This ensures identical output format regardless of connection method\n */\nexport function toToolResult(response: ExtensionResponse): ToolResult {\n // If response has content array, use it (already MCP formatted)\n if (Array.isArray(response?.content) && response.content.length > 0) {\n const result: ToolResult = {\n content: response.content,\n };\n\n if (response._meta) {\n result._meta = response._meta;\n }\n\n if (response.isError) {\n result.isError = true;\n }\n\n return result;\n }\n\n // Error response without content\n if (response?.ok === false || response?.isError || response?.error) {\n return {\n content: [\n {\n type: 'text',\n text: response.error || 'Unknown error',\n },\n ],\n isError: true,\n };\n }\n\n // Fallback: no content returned\n return {\n content: [\n {\n type: 'text',\n text: 'No content returned from extension',\n },\n ],\n isError: true,\n };\n}\n\n/**\n * Create an error ToolResult\n */\nexport function toErrorResult(message: string, error?: unknown): ToolResult {\n const errorText = error\n ? `${message}: ${error instanceof Error ? error.message : String(error)}`\n : message;\n\n return {\n content: [\n {\n type: 'text',\n text: errorText,\n },\n ],\n isError: true,\n };\n}\n","/**\n * Shared parameter sanitization functions\n * Used by both mcp-server and mcp-web-client for consistent behavior\n */\n\nconst trimString = (value: unknown): string => {\n return typeof value === \"string\" ? value.trim() : value !== undefined && value !== null ? String(value).trim() : \"\";\n};\n\nconst UNSAFE_PROTOCOL_PATTERN =\n /^(javascript:|data:|file:|vbscript:|chrome:|edge:|safari-extension:|moz-extension:|opera:)/i;\n\nconst normalizeDetailLevel = (value: unknown): \"shorten\" | \"all\" => {\n const s = typeof value === \"string\" ? value.toLowerCase() : \"\";\n if (s === \"shorten\" || s === \"all\") return s;\n return \"shorten\";\n};\n\nconst VALID_SNAPSHOT_REF_PATTERN = /^s\\d+(?:f\\d+)?e\\d+$/i;\nconst BACKEND_REF_PATTERN = /^backend:\\d+$/i;\n\nconst refFormatError = (ref: string, label?: string): string | undefined => {\n if (!ref) return undefined;\n if (VALID_SNAPSHOT_REF_PATTERN.test(ref)) return undefined;\n if (BACKEND_REF_PATTERN.test(ref)) return undefined;\n\n const prefix = label ? `${label}: ` : \"\";\n return `${prefix}Invalid element reference \"${ref}\". Use format s{snapshot}e{element} or s{snapshot}f{frame}e{element}, e.g., \"s1e1\" or \"s1f1e5\".`;\n};\n\nexport function sanitizeClickParams(params: Record<string, unknown>) {\n const target = trimString(params?.target);\n return {\n target,\n error: refFormatError(target),\n };\n}\n\nexport function sanitizeTypeParams(params: Record<string, unknown>) {\n const target = trimString(params?.target);\n return {\n target,\n text:\n typeof params?.text === \"string\"\n ? params.text\n : params?.text !== undefined && params?.text !== null\n ? String(params.text)\n : undefined,\n clear: params?.clear !== false,\n submit: params?.submit === true,\n error: refFormatError(target),\n };\n}\n\nexport function sanitizeInputParams(params: Record<string, unknown>) {\n const target = trimString(params?.target);\n return {\n target,\n value: params?.value,\n values: Array.isArray(params?.values)\n ? (params.values as unknown[]).map((v: unknown) => String(v))\n : undefined,\n error: refFormatError(target),\n };\n}\n\nexport function sanitizeDragParams(params: Record<string, unknown>) {\n const start = trimString(params?.start);\n const end = trimString(params?.end);\n const startError = refFormatError(start, \"Start target\");\n const endError = startError ? undefined : refFormatError(end, \"End target\");\n return {\n start,\n end,\n error: startError || endError,\n };\n}\n\nexport function sanitizeNavigateParams(params: Record<string, unknown>) {\n const target = trimString(params?.target);\n if (!target) {\n return { target: \"\" };\n }\n\n const lowered = target.toLowerCase();\n if (lowered === \"back\" || lowered === \"backward\" || lowered === \"forward\") {\n return { target: lowered };\n }\n\n if (UNSAFE_PROTOCOL_PATTERN.test(target)) {\n return { target: \"\", error: \"Unsafe navigation target rejected\" };\n }\n\n return { target };\n}\n\nexport function sanitizeScrollParams(params: Record<string, unknown>) {\n const target = trimString(params?.target);\n return {\n direction: params?.direction,\n amount: params?.amount,\n target,\n error: refFormatError(target),\n };\n}\n\nexport function sanitizeSnapshotParams(params: Record<string, unknown>) {\n return {\n fullPage: params?.fullPage !== false,\n detailLevel: normalizeDetailLevel(params?.detail ?? \"shorten\"),\n };\n}\n\nexport function sanitizeScreenshotParams(params: Record<string, unknown>) {\n return {\n includeRefs: params?.includeRefs === true,\n detailLevel: normalizeDetailLevel(params?.detail ?? \"shorten\"),\n };\n}\n","/**\n * Automation tools: click, type, drag\n * Shared between mcp-server and mcp-web-client\n */\n\nimport { getToolSchema } from './schemas.js';\nimport { toToolResult, toErrorResult } from './handlers.js';\nimport { sanitizeClickParams, sanitizeTypeParams, sanitizeDragParams } from '../utils/sanitize.js';\nimport type { Tool, ToolHandlerOptions } from '../types/index.js';\n\nexport function createBrowserClick(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_click'),\n handle: async (params) => {\n const { error, target } = sanitizeClickParams(params);\n if (error) {\n return toErrorResult(error);\n }\n\n const response = await options.call('browser_click', { target });\n return toToolResult(response);\n },\n };\n}\n\nexport function createBrowserType(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_type'),\n handle: async (params) => {\n const { error, ...sanitized } = sanitizeTypeParams(params);\n if (error) {\n return toErrorResult(error);\n }\n\n const response = await options.call('browser_type', sanitized);\n return toToolResult(response);\n },\n };\n}\n\nexport function createBrowserDrag(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_drag'),\n handle: async (params) => {\n const { error, ...sanitized } = sanitizeDragParams(params);\n if (error) {\n return toErrorResult(error);\n }\n\n const response = await options.call('browser_drag', sanitized);\n return toToolResult(response);\n },\n };\n}\n","/**\n * Navigation tools: navigate, scroll\n * Shared between mcp-server and mcp-web-client\n */\n\nimport { getToolSchema } from './schemas.js';\nimport { toToolResult, toErrorResult } from './handlers.js';\nimport { sanitizeNavigateParams, sanitizeScrollParams } from '../utils/sanitize.js';\nimport type { Tool, ToolHandlerOptions } from '../types/index.js';\n\nexport function createBrowserNavigate(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_navigate'),\n handle: async (params) => {\n const { target, error } = sanitizeNavigateParams(params);\n if (error) {\n return toErrorResult(error);\n }\n\n if (!target) {\n return toErrorResult('No navigation target provided');\n }\n\n const lowered = target.toLowerCase();\n if (lowered === 'back' || lowered === 'backward') {\n const response = await options.call('browser_navigate', { direction: 'back' });\n return toToolResult(response);\n }\n\n if (lowered === 'forward') {\n const response = await options.call('browser_navigate', { direction: 'forward' });\n return toToolResult(response);\n }\n\n if (lowered === 'new') {\n const response = await options.call('browser_navigate', { target: 'new' });\n return toToolResult(response);\n }\n\n let url = target;\n if (url && !url.match(/^https?:\\/\\//i)) {\n url = `https://${url}`;\n }\n\n const forceNewTabOpen = params?.forceNewTabOpen === true;\n const response = await options.call('browser_navigate', { url, closeTab: false, forceNewTabOpen });\n return toToolResult(response);\n },\n };\n}\n\nexport function createBrowserScroll(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_scroll'),\n handle: async (params) => {\n const { error, ...sanitized } = sanitizeScrollParams(params);\n if (error) {\n return toErrorResult(error);\n }\n\n const response = await options.call('browser_scroll', sanitized);\n return toToolResult(response);\n },\n };\n}\n","/**\n * Observation tools: snapshot (now includes plain + ref overlay screenshots)\n * Shared between mcp-server and mcp-web-client\n */\n\nimport { getToolSchema } from './schemas.js';\nimport { toToolResult, toErrorResult } from './handlers.js';\nimport { sanitizeSnapshotParams } from '../utils/sanitize.js';\nimport type { Tool, ToolHandlerOptions } from '../types/index.js';\n\nexport function createBrowserSnapshot(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_snapshot'),\n handle: async (params) => {\n try {\n const { fullPage, detailLevel } = sanitizeSnapshotParams(params);\n const response = await options.call('browser_snapshot', {\n fullPage,\n detail: detailLevel,\n });\n\n // Track tabId if callback provided\n const tabId = response?._meta?.tabId ?? (response?.data as { tabId?: number })?.tabId;\n if (typeof tabId === 'number' && options.onTabId) {\n options.onTabId(tabId);\n }\n\n return toToolResult(response);\n } catch (err) {\n return toErrorResult('Snapshot failed', err);\n }\n },\n };\n}\n","/**\n * Tab management tools: tabs, tab_switch, close_tabs\n * Shared between mcp-server and mcp-web-client\n */\n\nimport { getToolSchema } from './schemas.js';\nimport { toToolResult, toErrorResult } from './handlers.js';\nimport type { Tool, ToolHandlerOptions } from '../types/index.js';\n\nexport function createBrowserTabs(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_tabs'),\n handle: async () => {\n const response = await options.call('browser_tabs', {});\n return toToolResult(response);\n },\n };\n}\n\nexport function createBrowserTabSwitch(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_tab_switch'),\n handle: async (params) => {\n const tabId = params?.tabId;\n\n if (typeof tabId !== 'number' || !Number.isInteger(tabId)) {\n return toErrorResult('tabId must be an integer');\n }\n\n const response = await options.call('browser_tab_switch', { tabId });\n return toToolResult(response);\n },\n };\n}\n\nexport function createBrowserCloseTabs(options: ToolHandlerOptions): Tool {\n return {\n schema: getToolSchema('browser_close_tabs'),\n handle: async () => {\n const response = await options.call('browser_close_tabs', {});\n return toToolResult(response);\n },\n };\n}\n","/**\n * Tools module\n * Contains tool schemas, handlers, and factory functions\n */\n\n// ============================================================================\n// Types (re-exported from types folder)\n// ============================================================================\n\nexport type {\n ToolName,\n DisabledToolName,\n InternalToolName,\n AllToolName,\n ToolSchema,\n ToolResultContent,\n ToolResult,\n Tool,\n ToolHandlerOptions,\n ExtensionCaller,\n ExtensionCallerResponse,\n TabIdTracker,\n} from '../types/index.js';\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\nexport {\n // Zod schemas for validation\n ClickSchema,\n TypeSchema,\n DragSchema,\n NavigateSchema,\n ScrollSchema,\n SnapshotSchema,\n TabsSchema,\n TabSwitchSchema,\n CloseTabsSchema,\n // Tool definitions with descriptions\n TOOL_DEFINITIONS,\n // Schema getters\n getToolSchema,\n getToolSchemas,\n getAllToolSchemas,\n TOOL_SCHEMAS,\n} from './schemas.js';\n\n// ============================================================================\n// Handlers\n// ============================================================================\n\nexport {\n type ExtensionResponse,\n toToolResult,\n toErrorResult,\n} from './handlers.js';\n\n// ============================================================================\n// Tool Factories\n// ============================================================================\n\nexport { createBrowserClick, createBrowserType, createBrowserDrag } from './automation.js';\nexport { createBrowserNavigate, createBrowserScroll } from './navigation.js';\nexport { createBrowserSnapshot } from './observation.js';\nexport { createBrowserTabs, createBrowserTabSwitch, createBrowserCloseTabs } from './tabs.js';\n\nimport type { Tool, ToolHandlerOptions } from '../types/index.js';\nimport { createBrowserClick, createBrowserType, createBrowserDrag } from './automation.js';\nimport { createBrowserNavigate, createBrowserScroll } from './navigation.js';\nimport { createBrowserSnapshot } from './observation.js';\nimport { createBrowserTabs, createBrowserTabSwitch, createBrowserCloseTabs } from './tabs.js';\n\n/**\n * Create all standard tools with the given options\n */\nexport function createAllTools(options: ToolHandlerOptions): Tool[] {\n return [\n // Automation\n createBrowserClick(options),\n createBrowserType(options),\n // createBrowserDrag(options), // Drag available but hidden from tool list\n\n // Navigation\n createBrowserNavigate(options),\n createBrowserScroll(options),\n\n // Observation\n createBrowserSnapshot(options),\n\n // Tab management\n createBrowserTabs(options),\n createBrowserTabSwitch(options),\n createBrowserCloseTabs(options),\n ];\n}\n","/**\n * Jan Browser MCP Server\n * Modular architecture inspired by browsermcp\n * Provides browser automation tools via WebSocket bridge to Chrome extension\n */\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { appendFileSync } from \"node:fs\";\nimport type { RawData } from \"ws\";\n\n// Connection layer (WebSocket bridge)\nimport {\n registerExtensionConnection,\n handleExtensionMessage,\n cleanupPendingCalls,\n removeExtensionConnection,\n getProfileState,\n closeAllConnections,\n callExtension,\n waitForBridgeConnection,\n hasExtensionConnection,\n setActiveTabId,\n} from \"./connection/bridge.js\";\n\n// Tools from shared package\nimport { createAllTools, type Tool } from \"@janhq/mcp-shared\";\n\n// Configuration\nlet bridgeHost = process.env.BRIDGE_HOST || \"127.0.0.1\";\nlet bridgePort = Number(process.env.BRIDGE_PORT || 17389);\nlet bridgeToken = process.env.BRIDGE_TOKEN || undefined;\nconst SERVER_VERSION = \"0.13.2\";\nconst SERVER_NAME = \"jan-browser-mcp\";\n\nlet stdioTransport: StdioServerTransport | null = null;\nlet shuttingDown = false;\n\n// CLI arguments\nconst args = process.argv.slice(2);\n\nfor (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--bridge-host\" || arg === \"-H\") {\n const value = args[i + 1];\n if (value) {\n bridgeHost = value;\n i++;\n }\n } else if (arg === \"--bridge-port\" || arg === \"-p\") {\n const value = args[i + 1];\n if (value) {\n const parsed = Number(value);\n if (!Number.isNaN(parsed) && parsed > 0 && parsed < 65536) {\n bridgePort = parsed;\n }\n i++;\n }\n } else if (arg === \"--bridge-token\") {\n const value = args[i + 1];\n if (value) {\n bridgeToken = value;\n i++;\n }\n } else if (arg === \"--bridge-url\") {\n const value = args[i + 1];\n if (value) {\n try {\n const parsed = new URL(value);\n bridgeHost = parsed.hostname || bridgeHost;\n if (parsed.port) {\n const portNumber = Number(parsed.port);\n if (!Number.isNaN(portNumber) && portNumber > 0 && portNumber < 65536) {\n bridgePort = portNumber;\n }\n }\n bridgeToken = parsed.searchParams.get(\"t\") || bridgeToken;\n } catch (error) {\n logToFile(`Invalid --bridge-url provided: ${value} (${(error as Error).message})`);\n }\n i++;\n }\n }\n}\n\n// Optional file logging\nconst LOG_FILE = process.env.MCP_LOG_FILE;\n\n// Helper to log without interfering with stdio transport\nfunction logToFile(message: string) {\n if (LOG_FILE) {\n try {\n appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${message}\\n`);\n } catch (e) {}\n }\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n const stack = error.stack ? `\\n${error.stack}` : \"\";\n return `${error.name}: ${error.message}${stack}`;\n }\n if (typeof error === \"string\") {\n return error;\n }\n try {\n return JSON.stringify(error);\n } catch (e) {\n return String(error);\n }\n}\n\nfunction logErrorToFile(message: string, error?: unknown) {\n if (error !== undefined) {\n logToFile(`ERROR: ${message}\\n${formatError(error)}`);\n } else {\n logToFile(`ERROR: ${message}`);\n }\n}\n\n// Log startup\nlogToFile(\n `jan-browser-mcp v${SERVER_VERSION} starting; bridge ws://${bridgeHost}:${bridgePort}`\n);\n\n// Create MCP server using the old API like browsermcp\nconst server = new Server(\n {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n);\n\n// Create tools with bridge caller\nconst allTools: Tool[] = createAllTools({\n call: async (tool, params) => {\n if (!hasExtensionConnection()) {\n await waitForBridgeConnection(4000);\n }\n return await callExtension(tool, params);\n },\n onTabId: setActiveTabId,\n});\n\n// Register tool list handler\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: allTools.map((tool) => tool.schema) };\n});\n\n// Register tool execution handler\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const tool = allTools.find((t) => t.schema.name === request.params.name);\n if (!tool) {\n logErrorToFile(`Tool \"${request.params.name}\" not found`);\n return {\n content: [\n { type: \"text\", text: `Tool \"${request.params.name}\" not found` },\n ],\n isError: true,\n };\n }\n\n try {\n const result = await tool.handle(request.params.arguments || {});\n return result;\n } catch (error) {\n logErrorToFile(\n `Tool \"${request.params.name}\" execution failed`,\n error,\n );\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: \"text\", text: errorMessage }],\n isError: true,\n };\n }\n});\n\n// WebSocket bridge setup\nconst wss = new WebSocketServer({ host: bridgeHost, port: bridgePort });\n\nfunction shutdown(code = 0) {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n\n try {\n closeAllConnections();\n } catch (error) {\n logErrorToFile(\"Error closing bridge connections during shutdown\", error);\n }\n\n try {\n wss.close();\n } catch (error) {\n logErrorToFile(\"Error closing WebSocket server during shutdown\", error);\n }\n if (stdioTransport && typeof stdioTransport.close === \"function\") {\n try {\n void stdioTransport.close();\n } catch (error) {\n logErrorToFile(\"Error closing stdio transport\", error);\n }\n }\n cleanupPendingCalls();\n process.exit(code);\n}\n\nconst handleSignal = (signal: string) => {\n logToFile(`Received ${signal}, shutting down MCP server`);\n shutdown(0);\n};\n\nprocess.on(\"SIGINT\", handleSignal);\nprocess.on(\"SIGTERM\", handleSignal);\n\nwss.on(\"listening\", () => {\n logToFile(`Bridge listening on ws://${bridgeHost}:${bridgePort}`);\n});\n\nwss.on(\"connection\", (ws: WebSocket, req) => {\n const requestUrl = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\n // Token authentication\n if (bridgeToken) {\n const token = requestUrl.searchParams.get(\"t\");\n if (token !== bridgeToken) {\n logToFile(\"Bridge rejected connection: invalid token\");\n ws.close(1008, \"Invalid token\");\n return;\n }\n }\n\n const profileId = requestUrl.searchParams.get(\"pid\") || \"default\";\n const profileLabel = requestUrl.searchParams.get(\"pl\");\n\n logToFile(`Browser extension connected to MCP bridge (profile: ${profileId})`);\n registerExtensionConnection(profileId, profileLabel, ws);\n\n try {\n const profileState = getProfileState();\n const handshake = {\n kind: \"hello\",\n serverVersion: SERVER_VERSION,\n activeProfileId: profileState.activeProfileId,\n profileCount: profileState.profileCount,\n profiles: profileState.profiles,\n };\n ws.send(JSON.stringify(handshake));\n } catch (error) {\n logToFile(`Failed to send handshake to extension: ${(error as Error).message}`);\n }\n\n ws.on(\"pong\", () => {\n logToFile(`Native WebSocket pong received for profile ${profileId}`);\n });\n\n ws.on(\"message\", (data: RawData) => {\n handleExtensionMessage(profileId, data);\n });\n\n ws.on(\"close\", () => {\n logToFile(`Browser extension disconnected from MCP bridge (profile: ${profileId})`);\n removeExtensionConnection(profileId);\n });\n\n ws.on(\"error\", (err) => {\n logToFile(`WebSocket error for profile ${profileId}: ${err.message}`);\n });\n});\n\nwss.on(\"error\", (err) => {\n logErrorToFile(\"Failed to start WebSocket server\", err);\n shutdown(1);\n});\n\n// Main function\nasync function main() {\n const transport = new StdioServerTransport();\n stdioTransport = transport;\n transport.onclose = () => {\n logToFile(\"Stdio transport closed; shutting down MCP server\");\n shutdown(0);\n };\n await server.connect(transport);\n logToFile(\"MCP server ready, exposing tools via stdio\");\n}\n\n// Run\nmain().catch((err) => {\n logErrorToFile(\"MCP server failed to start\", err);\n process.exit(1);\n});\n"],"names":["LOG_FILE","logToFile","formatError","shuttingDown","uuidv4","response","createBrowserClick","createBrowserType","createBrowserNavigate","createBrowserScroll","createBrowserSnapshot","createBrowserTabs","createBrowserTabSwitch","createBrowserCloseTabs","appendFileSync"],"mappings":"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"}Report false positiveEnvironment file access
Detected by automated pattern matching (rule DE-002) with medium confidence. May be a false positive.
840: }
841: }
>>> 842: const LOG_FILE = process.env.MCP_LOG_FILE;
843: function logToFile(message) {
844: if (LOG_FILE) {Report false positiveEnvironment file access
Detected by automated pattern matching (rule DE-002) with medium confidence. May be a false positive.
791: let bridgeHost = process.env.BRIDGE_HOST || "127.0.0.1";
792: let bridgePort = Number(process.env.BRIDGE_PORT || 17389);
>>> 793: let bridgeToken = process.env.BRIDGE_TOKEN || void 0;
794: const SERVER_VERSION = "0.13.2";
795: const SERVER_NAME = "jan-browser-mcp";Report false positiveDecoded base64 content: J�b�'���ӭ�즊�
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���jX�x7�j)Kz��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���,�I��Sڭ��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���jX�x7�j)Kz��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���,�I��Sڭ��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: J�b�'���ӭ�즊�
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: J�b�'���ӭ�즊�
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ��y������l��!zf�
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ��y������l���z�ڶ*'
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���jX�x7�j)Kz��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���,�I��Sڭ��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���jX�x7�j)Kz��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���,�I��z{!��ڭ��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���jX�x7�j)Kz��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���,�I��Sڭ��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: ���,�I��Sڭ��
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: J�b�'���ӭ�즊�
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveDecoded base64 content: J�b�'���ӭ�즊�
Detected by automated pattern matching (rule DO-BAS) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.7 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.7 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.6 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (5.0 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.8 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.8 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (5.0 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.5 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.5 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.5 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.5 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.7 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.9 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.7 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveHigh-entropy string (4.7 bits/char) — possible encoded payload
Detected by automated pattern matching (rule EN-001) with medium confidence. May be a false positive.
Report false positiveScan History
| Date | Risk | Findings | Files | Duration |
|---|---|---|---|---|
| Feb 26, 2026 | critical | 39 | 5 | 0.00s |
| Feb 23, 2026 | critical | 39 | 5 | 0.00s |
| Feb 22, 2026 | critical | 39 | 5 | 0.00s |