@agent360/browser-mcp
Advanced tools
| { | ||
| "manifest_version": 3, | ||
| "name": "Agent360 Browser MCP", | ||
| "version": "1.16.0", | ||
| "description": "Control your real Chrome from Claude Code — navigate, click, fill, screenshot, solve CAPTCHAs. 24 tools, multi-session, human-in-the-loop.", | ||
| "version": "1.19.0", | ||
| "description": "Control your real Chrome from Claude Code — navigate, click, fill, set dates, dismiss overlays, autocomplete, upload, screenshot, solve CAPTCHAs. 33 tools, multi-session, human-in-the-loop.", | ||
| "permissions": [ | ||
@@ -7,0 +7,0 @@ "tabs", |
@@ -12,3 +12,3 @@ /** | ||
| const BASE_PORT = 9876; | ||
| const MAX_PORT = 9885; | ||
| const MAX_PORT = 9895; | ||
| const connections = new Map(); // port → WebSocket | ||
@@ -96,4 +96,18 @@ | ||
| // Listen for terminate signals from background.js (sent when last tab in a session closes) | ||
| chrome.runtime.onMessage.addListener((msg) => { | ||
| if (msg.type !== 'terminate_mcp_session' || typeof msg.port !== 'number') return; | ||
| const ws = connections.get(msg.port); | ||
| if (!ws) return; | ||
| try { | ||
| if (ws.readyState === WebSocket.OPEN) { | ||
| ws.send(JSON.stringify({ type: 'terminate' })); | ||
| } | ||
| } catch {} | ||
| try { ws.close(); } catch {} | ||
| // ws.onclose handler removes from connections + notifies background | ||
| }); | ||
| // Initial scan + frequent rescan for new servers | ||
| scanPorts(); | ||
| setInterval(scanPorts, 2000); |
+49
-9
@@ -17,6 +17,12 @@ #!/usr/bin/env node | ||
| import { execSync } from 'child_process'; | ||
| import { dirname } from 'path'; | ||
| import { dirname, join } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
| import { readFileSync } from 'fs'; | ||
| import { TOOLS, PROVIDER_PAGES } from './tools.js'; | ||
| // Read version from package.json — single source of truth, never drifts | ||
| const PKG_VERSION = JSON.parse( | ||
| readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'package.json'), 'utf8') | ||
| ).version; | ||
| // ── Auto-update on startup ───────────────────────────────────────────────── | ||
@@ -51,3 +57,3 @@ | ||
| const BASE_PORT = 9876; | ||
| const MAX_PORT = 9885; | ||
| const MAX_PORT = 9895; // 20 ports instead of 10 — zombies die within 5s via parent check | ||
| let extensionSocket = null; | ||
@@ -95,2 +101,8 @@ let activePort = null; | ||
| try { msg = JSON.parse(data.toString()); } catch { return; } | ||
| if (msg.type === 'terminate') { | ||
| process.stderr.write('[MCP] Terminate signal received from extension (last tab closed) — exiting\n'); | ||
| process.exit(0); | ||
| } | ||
| const { id, result, error } = msg; | ||
@@ -134,8 +146,12 @@ const p = pending.get(id); | ||
| function sendToExtension(method, params = {}, timeoutMs = 30000) { | ||
| async function sendToExtension(method, params = {}, timeoutMs = 30000, _retries = 5) { | ||
| // Retry if extension is temporarily disconnected (reconnects every 2s) | ||
| if (!extensionSocket || extensionSocket.readyState !== 1) { | ||
| if (_retries > 0) { | ||
| await new Promise(r => setTimeout(r, 1500)); | ||
| return sendToExtension(method, params, timeoutMs, _retries - 1); | ||
| } | ||
| throw new Error('Chrome extension not connected after 5 retries. Open Chrome and ensure Agent360 Browser MCP extension is installed.'); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| if (!extensionSocket || extensionSocket.readyState !== 1) { | ||
| reject(new Error('Chrome extension not connected. Open Chrome and ensure Agent360 Browser MCP extension is installed.')); | ||
| return; | ||
| } | ||
| const id = ++cmdId; | ||
@@ -213,2 +229,8 @@ const timer = setTimeout(() => { | ||
| ## Hard inputs — use the specialised tools first | ||
| - **Date inputs** → use browser_set_date (NOT browser_fill). Handles native date inputs, masked text inputs (MM/DD/YYYY etc.), AND calendar pickers (MUI, react-datepicker, AntD, Lexical/Meta). 3-path fallback with read-back verification. | ||
| - **Autocomplete / combobox** (Languages on Meta Ads, country selects, async dropdowns) → use browser_set_combobox (NOT browser_select_option). Types partial query, waits for filtered listbox, clicks option. Supports multi-value chips. | ||
| - **Drag-drop file zones without visible file input** → use browser_drop_file (NOT browser_upload_file). Finds hidden input in subtree/parent. | ||
| - **Annoying popups blocking the flow** (cookie banners, "Don't show again", Advantage+ tooltips, draft-confirm prompts) → call browser_dismiss_overlays before each major step. It only clicks safe close affordances by default; preserves forms with editable text fields. | ||
| ## When things fail | ||
@@ -219,2 +241,3 @@ - Element not found → try text-based selector instead of CSS | ||
| - CAPTCHA blocks page → use browser_ask_user, let human solve it | ||
| - browser_fill seemingly succeeds but value reverts → switch to browser_set_date or browser_set_combobox (most reverts are React-controlled validators) | ||
@@ -228,3 +251,3 @@ ## Extension updates | ||
| const mcpServer = new Server( | ||
| { name: 'agent360-browser', version: '1.16.0' }, | ||
| { name: 'agent360-browser', version: PKG_VERSION }, | ||
| { capabilities: { tools: {} } }, | ||
@@ -272,2 +295,6 @@ { instructions: INSTRUCTIONS }, | ||
| browser_solve_captcha: 'solve_captcha', | ||
| browser_set_date: 'set_date', | ||
| browser_dismiss_overlays: 'dismiss_overlays', | ||
| browser_set_combobox: 'set_combobox', | ||
| browser_drop_file: 'drop_file', | ||
| }; | ||
@@ -356,3 +383,16 @@ | ||
| // Detect Claude Code exit (stdin closes when conversation ends) | ||
| // Detect Claude Code exit — check if parent process is still alive | ||
| // stdin.on('end') doesn't work because MCP SDK's StdioServerTransport owns stdin | ||
| const parentPid = process.ppid; | ||
| const parentCheck = setInterval(() => { | ||
| try { | ||
| process.kill(parentPid, 0); // signal 0 = check if process exists | ||
| } catch { | ||
| process.stderr.write(`[MCP] Parent process ${parentPid} died — shutting down\n`); | ||
| clearInterval(parentCheck); | ||
| process.exit(0); | ||
| } | ||
| }, 5000); // check every 5 seconds | ||
| // Also listen for stdin close as backup | ||
| process.stdin.on('end', () => { | ||
@@ -359,0 +399,0 @@ process.stderr.write('[MCP] stdin closed — shutting down\n'); |
+1
-1
| { | ||
| "name": "@agent360/browser-mcp", | ||
| "version": "1.16.1", | ||
| "version": "1.19.0", | ||
| "description": "Browser MCP — control your real Chrome from Claude Code. 29 tools, CAPTCHA solving, file upload, multi-session, human-in-the-loop.", | ||
@@ -5,0 +5,0 @@ "mcpName": "io.github.Agent360dk/browser-mcp", |
+56
-3
@@ -59,3 +59,3 @@ /** | ||
| name: 'browser_fill', | ||
| description: 'Fill a form input field with a value. Supports CSS selectors AND text-based selectors. Auto-scrolls and focuses the element. Works on CSP-strict sites via Chrome Debugger API.', | ||
| description: 'Fill a form input field with a value. Supports CSS selectors AND text-based selectors. Auto-scrolls and focuses the element. Works on CSP-strict sites via Chrome Debugger API. For date inputs use browser_set_date, for autocomplete/combobox use browser_set_combobox.', | ||
| inputSchema: { | ||
@@ -124,3 +124,3 @@ type: 'object', | ||
| name: 'browser_select_option', | ||
| description: 'Select an option from a dropdown menu. Works with native <select> elements AND custom dropdowns (Angular Material, React Select, etc.). For custom dropdowns: clicks the trigger, waits for options, then clicks the matching option by text.', | ||
| description: 'Select an option from a dropdown menu. Works with native <select> elements AND custom dropdowns (Angular Material, React Select, etc.). For custom dropdowns: clicks the trigger, waits for options, then clicks the matching option by text. For autocomplete (typing filters options) use browser_set_combobox instead.', | ||
| inputSchema: { | ||
@@ -137,2 +137,55 @@ type: 'object', | ||
| { | ||
| name: 'browser_dismiss_overlays', | ||
| description: 'Dismiss visible popups, modals, tooltips, banners, and "Are you sure?"-style overlays in one call. Heuristic-based: finds close affordance via aria-label, text content (Skip/Cancel/Ikke nu/Don\'t show/Got it/Close), or × character button. Use when a flow is interrupted by unexpected dialogs (cookie banners, onboarding tooltips, draft-confirm prompts on Meta Ads, etc.). Returns list of what was dismissed.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| scope: { type: 'string', enum: ['non_critical', 'aggressive'], description: 'non_critical (default): skip dialogs containing editable form inputs (preserves user data). aggressive: dismiss everything.' }, | ||
| max_passes: { type: 'number', description: 'Number of dismissal passes (some overlays reveal others when closed). Default: 3' }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: 'browser_set_combobox', | ||
| description: 'Set value(s) on an autocomplete/combobox input. Handles the click → type query → wait for filtered listbox → click option flow as one MCP call. Supports multi-select (e.g., Languages on Meta Ads). Use when browser_select_option fails because options render lazily after typing.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| selector: { type: 'string', description: 'CSS selector for the combobox/autocomplete input' }, | ||
| value: { type: 'string', description: 'Single value to select (use this OR values)' }, | ||
| values: { type: 'array', items: { type: 'string' }, description: 'Array of values for multi-select. E.g. ["Danish", "English", "Swedish"]' }, | ||
| multi: { type: 'boolean', description: 'True if combobox accepts multiple values (chips). Default: auto-detected from presence of values array' }, | ||
| query_chars: { type: 'number', description: 'How many characters to type as filter query (default: 4 or full value length, whichever is smaller)' }, | ||
| wait_ms: { type: 'number', description: 'Max ms to wait for options listbox to appear after typing (default: 3000)' }, | ||
| }, | ||
| required: ['selector'], | ||
| }, | ||
| }, | ||
| { | ||
| name: 'browser_drop_file', | ||
| description: 'Upload a file by finding a hidden <input type="file"> within a drag-drop zone\'s subtree (or parent up to 2 levels). Use when browser_upload_file fails because the dropzone has no visible file input. Returns clear error if no input is found anywhere — pure drop-zones without backing inputs require manual handling.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| selector: { type: 'string', description: 'CSS selector for the drop-zone target element (e.g. ".upload-area")' }, | ||
| file: { type: 'string', description: 'Single absolute file path' }, | ||
| files: { type: 'array', items: { type: 'string' }, description: 'Array of absolute file paths' }, | ||
| }, | ||
| required: ['selector'], | ||
| }, | ||
| }, | ||
| { | ||
| name: 'browser_set_date', | ||
| description: 'Robustly set a date input — handles native <input type="date">, masked text inputs (e.g. MM/DD/YYYY), and calendar pickers (MUI, react-datepicker, AntD, Lexical/Meta). Tries native value-set, format-aware typing via Input.insertText, and ARIA-based picker navigation in sequence with read-back verification. Use instead of browser_fill when fill fails or for any input that opens a calendar widget.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| selector: { type: 'string', description: 'CSS selector for the date input element' }, | ||
| date: { type: 'string', description: 'ISO date string (YYYY-MM-DD), e.g. "2026-05-15"' }, | ||
| skip_picker: { type: 'boolean', description: 'If true, only try native + masked paths and skip calendar-picker navigation (default: false)' }, | ||
| }, | ||
| required: ['selector', 'date'], | ||
| }, | ||
| }, | ||
| { | ||
| name: 'browser_handle_dialog', | ||
@@ -313,3 +366,3 @@ description: 'Handle JavaScript alert(), confirm(), or prompt() dialogs. Call this BEFORE triggering the action that causes the dialog. Waits for the dialog to appear, then accepts or dismisses it.', | ||
| name: 'browser_upload_file', | ||
| description: 'Upload a file to a <input type="file"> element on the page. Uses Chrome Debugger API to set files programmatically — no dialog needed.', | ||
| description: 'Upload a file to a <input type="file"> element on the page. Uses Chrome Debugger API to set files programmatically — no dialog needed. For drag-drop zones without visible file input use browser_drop_file.', | ||
| inputSchema: { | ||
@@ -316,0 +369,0 @@ type: 'object', |
Sorry, the diff of this file is too big to display
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential malware
Supply chain riskAI has identified this package as malware. This is a strong signal that the package may be malicious.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
186228
32.38%3515
38.77%0
-100%12
33.33%