From bc80647ac3600ec74d440e7226ba7010ea216e20 Mon Sep 17 00:00:00 2001 From: noiemany Date: Tue, 29 Sep 2026 16:07:43 +0300 Subject: [PATCH 1/2] fix(navigate): support back/forward and Windows pipe in drive.mjs browser_navigate treated url back as a relative URL and landed on chrome-extension:///back, stranding the tab on a protected page. It now walks the tab history via chrome.tabs.goBack/goForward and reports the URL it actually landed on. scripts/drive.mjs connected to ~/.browser-controller/daemon.sock on every platform; on Windows the daemon listens on the named pipe \\.\pipe\browser-controller (daemon-config.ts), so it always failed with ENOENT. Co-Authored-By: Claude Opus 5.5 --- extension/handlers/navigation.js | 17 ++++++++++++++--- mcp-server/src/tools/navigate.ts | 2 +- scripts/drive.mjs | 5 ++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/extension/handlers/navigation.js b/extension/handlers/navigation.js index dda1f43..7d9b69f 100644 --- a/extension/handlers/navigation.js +++ b/extension/handlers/navigation.js @@ -19,7 +19,8 @@ export async function getActiveTab() { } export async function handleNavigate(params, _sessionId, _agentName, signal) { - const { url, waitUntil = 'load', tabId, snapshot: wantSnapshot = true } = params; + let { url } = params; + const { waitUntil = 'load', tabId, snapshot: wantSnapshot = true } = params; // navigate is the one page tool allowed to omit tabId → active tab fallback. const tab = tabId != null ? await resolveTab(tabId) : await getActiveTab(); @@ -27,8 +28,15 @@ export async function handleNavigate(params, _sessionId, _agentName, signal) { // so `chrome.tabs.onUpdated` never fires `status === 'complete'` and the wait // below would hang for the full 55s timeout. Detect this case and skip the // wait entirely — the SPA router updates client-side near-instantly. + // "back"/"forward" walk the tab's history instead of being treated as a + // relative URL (which used to land on chrome-extension:///back). + const historyStep = url === 'back' || url === 'forward' ? url : null; + const go = () => historyStep === 'back' ? chrome.tabs.goBack(tab.id) + : historyStep === 'forward' ? chrome.tabs.goForward(tab.id) + : chrome.tabs.update(tab.id, { url }); + const currentTab = await chrome.tabs.get(tab.id); - const hashOnly = isHashOnlyChange(currentTab.url, url); + const hashOnly = !historyStep && isHashOnlyChange(currentTab.url, url); // A promise that rejects when this call is cancelled (client gone / timeout // forwarded from the bridge). Handlers that await long-running operations @@ -84,7 +92,7 @@ export async function handleNavigate(params, _sessionId, _agentName, signal) { }; chrome.tabs.onUpdated.addListener(listener); if (wantDcl) pollTimer = setInterval(probeReady, 150); - chrome.tabs.update(tab.id, { url }).catch((err) => { + go().catch((err) => { chrome.tabs.onUpdated.removeListener(listener); if (pollTimer) clearInterval(pollTimer); reject(err); @@ -111,6 +119,9 @@ export async function handleNavigate(params, _sessionId, _agentName, signal) { // Return the snapshot inline ONLY if the caller asked for it (default true). // Skipping it (snapshot:false) saves a large chunk of tokens when the agent // intends to call browser_snapshot itself or doesn't need the tree yet. + // For back/forward report where we actually landed, not the keyword. + if (historyStep) url = (await chrome.tabs.get(tab.id)).url; + if (!wantSnapshot) { return { url, status: 'navigated', tabId: tab.id }; } diff --git a/mcp-server/src/tools/navigate.ts b/mcp-server/src/tools/navigate.ts index 14bbecf..949ef03 100644 --- a/mcp-server/src/tools/navigate.ts +++ b/mcp-server/src/tools/navigate.ts @@ -6,7 +6,7 @@ export const navigateTool: ToolDefinition = { name: 'browser_navigate', summary: 'Navigate a tab to a URL (+ optional inline snapshot)', description: 'Navigate to a URL in a browser tab. If tabId is omitted, navigates the active tab (or use browser_tabs create to open a new one). By default also returns a compact snapshot of the new page so you can act immediately; set snapshot:false to skip it and save tokens.', inputSchema: z.object({ - url: z.string().describe('The URL to navigate to'), + url: z.string().describe('The URL to navigate to, or "back" / "forward" to move through the tab history'), tabId: optionalTabId(), waitUntil: z .enum(['load', 'domcontentloaded']) diff --git a/scripts/drive.mjs b/scripts/drive.mjs index 421722b..7f051c8 100644 --- a/scripts/drive.mjs +++ b/scripts/drive.mjs @@ -13,7 +13,10 @@ import path from 'node:path'; const STATE = path.join(os.homedir(), '.browser-controller'); const token = JSON.parse(fs.readFileSync(path.join(STATE, 'token.json'), 'utf8')).token; -const sockPath = path.join(STATE, 'daemon.sock'); +// Must match daemon-config.ts: Windows uses a named pipe, not a socket file. +const sockPath = process.platform === 'win32' + ? '\\\\.\\pipe\\browser-controller' + : path.join(STATE, 'daemon.sock'); const tool = process.argv[2]; if (!tool) { console.error('usage: drive.mjs [json params]'); process.exit(1); } From fb724e444911f062d0aa16eff05f6d83327b9e14 Mon Sep 17 00:00:00 2001 From: noiemany Date: Tue, 29 Sep 2026 16:19:14 +0300 Subject: [PATCH 2/2] chore(release): bump version to 2.2.1 Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- extension/manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- server.json | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 23fc560..92cdcc6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

CI - v2.2.0 + v2.2.1 License: MIT Node >= 20 TypeScript strict diff --git a/extension/manifest.json b/extension/manifest.json index 80c93f3..479f281 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Browser Controller", - "version": "2.2.0", + "version": "2.2.1", "description": "Let AI agents control your real browser - your tabs, your sessions, your logins", "icons": { "16": "icons/icon16.png", diff --git a/package-lock.json b/package-lock.json index d116ab2..39fee35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "browser-controller", - "version": "2.2.0", + "version": "2.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "browser-controller", - "version": "2.2.0", + "version": "2.2.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", diff --git a/package.json b/package.json index 0976c4c..014e6b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browser-controller", "mcpName": "io.github.noiemany/browser-controller", - "version": "2.2.0", + "version": "2.2.1", "description": "MCP server + Chrome extension that gives AI agents control of your real browser with existing sessions and logins", "type": "module", "bin": { diff --git a/server.json b/server.json index 6240a00..660b249 100644 --- a/server.json +++ b/server.json @@ -6,12 +6,12 @@ "url": "https://github.com/compnew2006/browser-controller", "source": "github" }, - "version": "2.2.0", + "version": "2.2.1", "packages": [ { "registryType": "npm", "identifier": "browser-controller", - "version": "2.2.0", + "version": "2.2.1", "transport": { "type": "stdio" }