browser_navigate
Navigate to any URL in the browser
url必填tabIdsession
io.github.abhinav162由 GitHub 账号 abhinav162 发布v1.12.0更新于 Oct 27, 2026
Drive and scrape your existing Chrome profile from any AI assistant — no headless browser.
PolterTab 通过本地 MCP 服务器加 Chrome 扩展,让 AI 助手操作用户已登录的 Chrome 浏览器并抓取页面内容。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
参照 仓库 中的启动说明。
Let any AI agent use the Chrome you're already logged into. PolterTab is a Chrome extension plus a local MCP (Model Context Protocol) server.
[Add to Chrome — available in the Chrome Web Store] [npm version] [npm downloads] [license]
A Chrome extension + Model Context Protocol (MCP) server. The AI acts inside the browser you already use: your real profile, your cookies, your logged-in sessions. Install the extension, point your MCP client at the server, done — it works with Claude Desktop, Cursor, or any MCP-compatible agent.
Several agents can point at the same browser at once — the first server to claim the WebSocket port serves the extension and the rest proxy through it. See Running several agents at once.
PolterTab runs entirely on your machine. There is no PolterTab server, account, or telemetry.
127.0.0.1 and ::1, port 7822 by default). The server refuses connections from web pages — browser connections must come from a Chrome extension (chrome-extension://) origin — and only the Chrome Web Store build (trusted automatically) or an extension you have paired can hold that connection, change upload settings, or read the logs. (architecture)browser_upload_file refuses every path until you add a folder with poltertab uploads allow <dir>, and sensitive locations are always blocked and cannot be allowlisted. Run poltertab uploads to see the current list.npm publish --provenance, so npm records a signed, verifiable link from the published tarball back to the GitHub Actions build that produced it.registry.npmjs.org); poltertab setup additionally does a one-time Chrome Web Store version lookup (clients2.google.com). Both only compare versions to warn about a server/extension mismatch — no page content or personal data is sent. Turn both off with POLTERTAB_NO_UPDATE_CHECK=1.From 1.12.0, only the Chrome Web Store extension or one you have paired can control the browser; an unpacked install pairs once with
poltertab uploads pair.
To report a vulnerability, see SECURITY.md.
Requires Node 18 or newer.
The wizard asks whether to install for all projects or just the current one, then puts three things in place:
browser-navigation-strategy skill, so the agent knows how to drive a
browser that fights backCLAUDE.md section, so it knows the tools existclaude mcp addIt is safe to re-run — each step detects its own prior work, so an upgrade or a second pass will not duplicate anything.
Prefer not to install globally? npx poltertab setup does the same thing.
Add to Chrome from the Chrome Web Store. Chrome keeps a Store extension up to date on its own, so this is the install path for everyone who is not developing the extension itself.
Then restart your AI tool so it picks up the new tools.
Use this when you are changing the extension or cannot reach the Web Store. An unpacked extension never updates on its own; you reload it from a newer zip yourself.
unzip poltertab-extension-vX.zip -d poltertab-extension, or double-click on macOS — which creates a folder automatically)chrome://extensions/manifest.json)poltertab uploads pair and enter the 4-character code the extension shows. (Store installs are trusted automatically and skip this.)The wizard targets Claude Code. Any other MCP client works too — register
poltertab as a stdio command:
Using Cursor? One-click install (adds the stdio entry above):
Running from a clone instead of npm? Point the client at the file directly:
{"command": "node", "args": ["/absolute/path/to/poltertab/mcp-server/index.js"]}
The server exposes 28 tools to the LLM (all prefixed with browser_). Every
page-facing tool also takes optional tabId and session to target a specific
tab — omit both and PolterTab uses the agent's current tab: the last one it
navigated, opened or switched to. The first browser_navigate (and the first
browser_navigate of each new session) opens its own tab rather than
commandeering the one you are looking at.
Every tool here takes output_file, which writes the payload to
~/.poltertab/downloads/ and returns only a summary — row count, field names,
fill rates and two sample records. Use it for anything bigger than a glance; the
raw payload should never be the default path into a context window.
browser_scrape returns a flat list, which loses track of which value belongs
to which record. That is fine for one element and dangerous for ninety: the
moment a field is optional, a flat scrape of names against phone numbers shifts
every later phone up a row and mis-assigns the rest of the page.
browser_extract takes a record container and resolves each field inside it:
null. It never shifts.sel is relative to the record root; omit it (or use ".") for the root itself.get is text (default), href, src, or any attribute name.anchor names the field a real record always has. Records missing it — the
placeholder cards that pad the last page of a listing — are dropped and
counted in dropped rather than emitted as rows of nulls.The result reports what happened, so an empty column cannot pass for an answer:
That warning is the loosening probe. Any field that comes back entirely empty is re-checked page-wide, because the container that looks like the record often excludes a sibling holding some of the fields — and "0 socials" reads as "these people have no social accounts" rather than "your selector scope is wrong".
browser_extract_all takes the same spec plus a url_template containing
{page}, and runs the loop itself. It dedups on key and halts on the first of:
limit_reached, empty_page, duplicate_page (a page whose records repeat an
earlier one — what a site does when it ignores an unrecognised page-size
param), fill_rate_deviation (a page whose fill rates collapse against the
first page's baseline, which is how a spec learned on page 1 quietly degrades on
page 10), or max_pages. It always reports which condition fired and returns
everything collected up to that point, so a halt never costs you the run.
.csv and .jsonl output files are written in those formats.
For sites whose DOM is obfuscated or virtualized, read the JSON driving the UI instead of scraping elements that may not exist.
A session is a named tab. Giving each agent its own session stops concurrent agents fighting over one tab; closed tabs are recreated from the stored URL on next use.
One agent can drive many tabs. Each agent (and each secondary server) has its
own current tab; page-facing tools without tabId/session act there. An
explicit tabId acts on that tab without changing the current one; a tabId
Chrome does not have returns tab_not_found on any tool that takes one. If the
current tab is closed, untargeted calls fail with a message naming the closed
tab instead of quietly acting on another one.
Configuration — add upload_allowed_paths to ~/.poltertab/config.json before using browser_upload_file:
Always-blocked regardless of the allowlist: ~/.ssh, ~/.aws, ~/.gnupg, ~/.config, /etc, /private/etc, /System, /private/System, /Library, /private/Library, /proc, home dotfiles. Note: /tmp (and its macOS realpath /private/tmp) is not blocked and can be added to upload_allowed_paths normally.
Selectors are resolved in order:
@e12, as returned by browser_snapshot. Resolves to the
exact node the snapshot described, and beats a generated class chain.#id, .class, div > span//div[@class="foo"]A miss on all four is retried with shadow-root piercing (open roots, one level deep) and top-level same-origin iframe search. Nested iframes (iframe inside iframe) and cross-origin iframes are not supported; the tool returns a clear error in those cases.
Running several agents at once:
The first MCP server to bind port 7822 becomes the primary and owns the
connection to the extension. Later servers find the port taken and switch
themselves into secondary mode, proxying their commands through the primary,
so concurrent agents share one browser with no extra configuration. The
primary tags each secondary's commands with its id, and the extension keeps a
separate current tab per agent, so agents get their own tabs. Up to
POLTERTAB_MAX_SECONDARIES v2 secondaries are accepted (default 50); if the
primary exits, one secondary promotes itself to replace it. The extension popup
lists whichever agents are connected.
How many agents can share one browser:
Up to 50 v2 secondaries (one process each) plus the primary can share one
browser. Raise or lower the ceiling with POLTERTAB_MAX_SECONDARIES. An agent
that is idle for POLTERTAB_IDLE_EVICTION_MS (default 5 minutes) is evicted
with close code 4001 to free the slot; it reconnects automatically the next time
it sends a command, so eviction is transparent to the agent. An agent whose
major version does not match the primary's is refused with close code 4002 —
see Upgrade path below.
Command queue:
The primary limits how many commands it sends to the extension at once
(POLTERTAB_MAX_CONCURRENCY, default 6). When that limit is reached, a v2
secondary's command is queued per-secondary (up to POLTERTAB_QUEUE_DEPTH
slots, default 3). The queued command is acknowledged immediately with
{"type":"queued","id":"...","position":N} so the caller knows its command is
pending, not lost. A v2 secondary re-arms its 35 s timeout on this
acknowledgement and again on {"type":"dispatched","id":"..."} when the command
leaves the queue, so queue wait never consumes the execution budget. A command
that arrives when the per-secondary queue is already full is refused immediately
with [queue_full action=X]. The primary itself queues its own direct commands
under the same ceiling. A queued command is dropped with [queue_timeout action=X]
when the caller's 35 s budget is nearly spent (a 2 s margin keeps the drop ahead
of the caller's own timer). POLTERTAB_QUEUE_TIMEOUT_MS (default 60 s) is a
separate GC backstop for commands in a queue that never gets dequeued; it does
not extend queue wait time.
/status endpoint:
GET http://127.0.0.1:7822/status returns a JSON snapshot of the primary's
queue state, connected agents, and wait-time percentiles (p50/p95/p99). It is
restricted to loopback callers (127.0.0.1, ::1, or ::ffff:127.0.0.1) and
to requests whose Host header names localhost or a loopback address; all
others receive 404.
Extension reconnection: The Chrome extension reconnects automatically when the bridge server restarts or the WebSocket drops. It dials immediately on disconnect, then backs off 1 s, 2 s, 4 s … up to 30 s, with an alarm that wakes the service worker if it was suspended mid-schedule. While reconnecting, commands return:
[extension_reconnecting port=7822 elapsed=Xs] — the extension disconnected
and is on its way back. Retry shortly.[extension_never_connected port=7822] — the extension has never connected to
this server instance. Check that the extension is installed, enabled, and
pointed at the right port.Close codes:
Upgrade path (1.x → 1.8):
PolterTab 1.8 introduces a version handshake. A secondary whose major version
does not match the primary's receives close code 4002 and stops retrying; updating
the npm package (so all sessions run the same major) resolves it. The Chrome
extension should also be updated at the same time to get the PR-4 reconnect
behaviour. Run poltertab doctor to check for skew before upgrading. A 1.7.x
secondary that has not upgraded does not receive 4002 (it does not speak the
handshake) and continues to work as an unversioned legacy secondary; it bypasses
the command queue and is never idle-evicted.
Env vars:
Changing the WebSocket Port:
Only needed when something that isn't PolterTab already holds 7822 —
PolterTab servers share that port by design (see above). Both sides must move:
Click the Extension icon in Chrome -> Settings.
Change the WebSocket Port to a free port (e.g. 7824) and click Save —
the extension reconnects immediately, no reload needed.
Update your MCP Server configuration to pass the new port.
MCP_BROWSER_WS_PORT=7824 node index.jsnode index.js --port 7824Picker uploads fail while DevTools is open:
Chrome allows only one debugger at a time per tab. When DevTools is open on the target tab, browser_upload_file (method "picker") fails with "Another debugger is already attached". Close DevTools on that tab before running a picker upload.
Adding debugger permission requires re-approving the extension:
Loading or reloading the extension folder after the debugger permission was added prompts Chrome to ask for re-approval — the extension is disabled until you click Keep or Allow on the warning dialog in chrome://extensions/. Unpack-loaded extensions do not auto-update, so this only happens when you explicitly reload or update the extension folder.
Extension not connecting:
chrome://extensions/Commands timing out / Restricted Pages:
chrome:// URLs, the Chrome Web Store, or certain restricted pages. The MCP server will cleanly report "Cannot interact with this page" back to the agent in these cases.Reports the installed version, whether npm has a newer one, and — the part that matters — whether the two halves agree:
The npm package updates when you run npm update -g poltertab. The extension
installed from the Chrome Web Store
updates itself, like any Store extension. One loaded unpacked never updates on
its own — Chrome only auto-updates what came from the Web Store. So the
interesting failure is not a missing update, it is skew: a newer server sending
a command an older extension does not implement (or the reverse, while Chrome
has updated the extension and you have not yet run npm update), which
surfaces as an element mysteriously not being found.
Both halves ship as one version, so a mismatch is unambiguous. The extension reports its version when it connects; the server compares, logs it, and shows it in the extension popup. If either half is behind, the first tool response of a session says so once, so you hear about it without having to think to check.
Update checks hit registry.npmjs.org at most once a day, cached in
~/.poltertab/update-check.json, and never block anything. Set
POLTERTAB_NO_UPDATE_CHECK=1 to turn them off — skew detection is local and
keeps working either way.
poltertab doctor exits non-zero on skew, so CI or a shell prompt can act on it.
Nothing the server accumulates is stored inside the package:
This matters for a global npm install. The package sits in node_modules/, so
anything written beside the code is deleted by the next npm update -g — site
memory would read as amnesia after every upgrade, and scraped payloads would be
buried somewhere you would never look for them.
Set POLTERTAB_HOME to move the whole tree. Upgrading from a version that kept
memory in mcp-server/navigation_memory/? It is copied forward on first start,
and an existing note at the destination always wins.
Publishing is driven by GitHub Releases. The version string decides the npm channel, so the git tag is the only thing you have to get right:
To cut one:
npm version runs scripts/sync-manifest-version.js, which keeps
chrome-extension/manifest.json in step. The two files cannot hold the same
string — Chrome rejects a manifest version with a prerelease identifier — so the
numeric base goes in version and the full string in version_name.
Publishing the release then runs the workflow: tests, a version-consistency
check, npm publish --provenance on the resolved tag, and the extension zip
gets built and attached to the release.
The checks refuse to publish rather than guess. A tag that disagrees with
package.json, a manifest left behind, or a stable version on a release marked
pre-release all fail the run — that last one because it would otherwise land on
latest and become the default install for everyone.
Use the workflow's manual trigger (dry_run: true) to rehearse all of it
without publishing.
No framework and no browser needed. Two suites run:
mcp-server/test/run.js — content-script injection idempotence, shadow DOM
piercing, cross-frame search, the tab-navigation load race, and the MCP server
end to end. It spawns real server processes against a fake extension on port
7931, so it never disturbs a PolterTab already running on 7822.test/setup.test.js — the installer: scope resolution, skill install, and
CLAUDE.md idempotence. Every case runs in a throwaway temp directory, so a
leaked write cannot reach your real ~/.claude.来源:README.md,提交 fca135f
Navigate to any URL in the browser
url必填tabIdsessionClick an element on the page. Returns the element's `fingerprint` (pass it back later to survive a selector change) and `healed:true` if the selector had drifted and the element was relocated.
tabIdsessionselector必填fingerprintFill an input field with text. Returns the field's `fingerprint` (pass it back later to survive a selector change) and `healed:true` if the selector had drifted and the field was relocated.
tabIdvalue必填submitsessionselector必填fingerprintScrape the page or specific elements. For repeating records (cards, rows, listings) use browser_extract instead — it keeps fields grouped per record.
tabIdfieldssessionmax_textmultipleselectorattributeoutput_fileExtract repeating records (cards, rows, listings) with fields grouped per record. Fields resolve INSIDE each record root and a missing field yields null instead of shifting later records' values. Returns fill rates and warns when a field is empty inside the record scope but matches page-wide (record boundary too narrow). `record` and `fields` are optional when a recipe has already been learned for this page shape: omit them to replay it, and the result names it in `used_recipe` (with `stale: true` if the page no longer matches what was learned). `remember` names what a fresh spec is stored as.
probetabIdanchorfieldsreciperecordsessionvariantmax_textrememberoutput_filePaginate and extract in one call, with no model round-trip per page. Takes browser_extract's spec plus a URL template — the spec is optional when a recipe was learned for that page shape, and a recipe learned here stores the template so a later call can replay both. Walks pages, dedups on a key, and halts on: limit reached, empty page, a page whose records repeat an earlier page's (the trap where ignored page-size params silently return page 1 again), fill rates collapsing against page 1's baseline, or max_pages. Always reports which condition fired and returns everything collected so far.
keylimitanchorfieldsoffsetreciperecordsessionvariantmax_textremembermax_pages另有 4 个Capture visible tab as base64 PNG. This action will focus the target tab.
tabIdsessionScroll the page
tabIdamountsessionselectordirection必填Hover over an element
tabIdsessionselector必填Get text content of an element
tabIdsessionmax_textselector必填output_fileGet page title and URL
tabIdsessionGet page URL
tabIdsessionGet a snapshot of the DOM. Large pages run to tens of KB — narrow it with interactive_only/max_nodes, or send it to output_file, before spending the context on it.
tabIdsessionmax_depthmax_nodesoutput_fileinteractive_onlyGet captured raw JSON data (GraphQL/XHR) for the current tab, bypassing DOM virtualization
cleartabIdsessionoutput_fileSet URL substrings to determine which network requests are captured in the MAIN world.
tabIdsessionpatterns必填Scroll the page and wait for new network data to load (handles lazy-loading)
tabIdamountsessiondirection必填Create or track a named browser session (tab): opens `url` in a new tab, adopts `tabId`, or with neither binds the tab the user is looking at. It becomes the current tab.
urlname必填tabIdSwitch to a different tracked session
name必填List all tracked sessions
无需输入参数。
Close a tracked session and its tab. Tabs not opened by PolterTab — including ones adopted via browser_session_create({tabId}) — require the user's permission: the tool returns code needs_user_permission with the tab's id, title and url; ask the user, then retry with force: true.
name必填forceGet this agent's current tab: its id, URL, title and session name, or closedTabId if the tab it was on has been closed
无需输入参数。
Open a new tab and make it this agent's current tab. Later calls without tabId/session act on it. Returns its tabId.
urlsessionbackgroundList the tabs this agent can work with: every tab PolterTab opened or bound to a session, marking which one is current. all: true lists every open tab in the browser (ids, URLs and titles only).
allMake another tab this agent's current tab, by tabId or session name. Does not bring it to the front unless focus: true.
focustabIdsessionClose a tab by tabId or session name (default: the current tab). Tabs not opened by PolterTab — including ones adopted via browser_session_create({tabId}) — require the user's permission: the tool returns code needs_user_permission with the tab's id, title and url; ask the user, then retry with force: true. A tabId that does not exist in Chrome returns tab_not_found. Closing the current tab leaves no current tab; untargeted calls then error until you switch or navigate.
forcetabIdsessionGet navigation memory for a website domain: the obstacles and fixes saved for it, plus any extraction recipes learned there — their fields, their variants, and the steps that were observed before each one worked. The steps are observations, not a script: reissue one only if it still fits the page.
domainhostnameSave a new navigation memory, obstacle, or fix for a specific website domain
domainhostnameobstacle必填solution必填Upload one or more local files to the current page. Works with plain file inputs (visible or hidden), custom upload buttons, shadow-DOM inputs (open roots only, one level deep), top-level same-origin iframes, and drag-and-drop zones. For file inputs and upload buttons, uses CDP (Chrome DevTools Protocol) to intercept the OS file picker — a brief debugging banner appears during the upload. For drop zones, dispatches a DataTransfer drag-and-drop event. Paths must be within directories configured in upload_allowed_paths in ~/.poltertab/config.json. Nested iframes (iframe inside iframe) and cross-origin (out-of-process) iframes are not supported and return a clear error.
paths必填tabIdmethodsessionselector必填已满足适用检查项的 90%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
Any MCP Agent (Claude Desktop, Cursor, custom agents) │ Stdio (MCP Protocol) ▼Node.js MCP Server (mcp-server/index.js) │ WebSocket (port 7822 by default) ▼PolterTab Chrome Extension │ Chrome APIs + DOM ▼ Your existing Chrome profilenpm install -g poltertabpoltertab setup{ "mcpServers": { "poltertab": { "command": "poltertab" } }}{ "record": ".agent-card", "fields": { "name": { "sel": "a.agent-card-name", "get": "text" }, "url": { "sel": "a.agent-card-name", "get": "href" }, "phone": { "sel": "a[href^='tel:']", "get": "href", "strip": "tel:" }, "socials": { "sel": "a.social-button", "get": "href", "many": true } }, "anchor": "url", "output_file": "agents.csv"}{ "count": 12, "dropped": 0, "fill_rates": { "name": 12, "phone": 10, "socials": 0 }, "warnings": ["socials: 0/12 within record scope, but 40 matches page-wide for a.social-button — record boundary likely too narrow"] }{ "upload_allowed_paths": ["/Users/you/Documents", "/Users/you/Downloads"]}"args": ["/absolute/path/to/poltertab/mcp-server/index.js", "--port", "7824"]poltertab/├── package.json # Published npm package (deps + bin)├── bin/│ ├── poltertab.js # Entry: bare = MCP server, `setup` = wizard│ └── setup.js # Installer (skill, CLAUDE.md, MCP registration)├── skills/│ └── browser-navigation-strategy/ # Shipped skill, installed by the wizard├── assets/│ └── claude-md-snippet.md # The CLAUDE.md section the wizard appends├── mcp-server/│ ├── index.js # MCP server (stdio) + WebSocket hub│ └── test/run.js # Extension + server regression suite├── test/│ └── setup.test.js # Installer regression suite├── chrome-extension/│ ├── manifest.json # MV3 manifest│ ├── background.js # WebSocket client, session manager, command router│ ├── content_script.js # DOM extractor + action executor (isolated world)│ ├── interceptor.js # fetch/XHR capture (main world)│ ├── popup.html/js # Connected-agents UI│ ├── options.html/js # Port configuration UI│ └── icons/└── README.mdpoltertab doctor server 1.1.0 extension 1.0.0 SKEW last seen 3m ago bridge listening on port 7822 state ~/.poltertab npm 1.2.0 available~/.poltertab/├── navigation_memory/ # Per-domain obstacles and fixes (browser_save_site_memory)└── downloads/ # output_file payloads from any read toolnpm version 1.2.0-beta.1 # bumps package.json, syncs the manifest, tagsgit push --follow-tagsgh release create v1.2.0-beta.1 --generate-notes --prereleasenpm installnpm test