关于 poltertab
PolterTab 是由本地 MCP 服务和 Chrome 扩展组成的工具,让 AI Agent 操作并读取已登录网站的现有浏览器配置。它提供页面交互、结构化抓取、网络捕获和标签页会话管理。
主要功能
- 01控制现有 Chrome 配置
- 02导航、点击、填写和滚动页面
- 03跨页面提取结构化记录
- 04捕获网络 JSON 响应
- 05通过命名会话管理标签页
适用场景
- 01自动处理已登录网站上的任务
- 02收集分页页面中的重复记录
- 03检查虚拟化页面界面背后的数据
MCP 相关
PolterTab 通过 stdio 连接 MCP 客户端,并由本地服务提供浏览器工具。服务通过本地 WebSocket 与 Chrome 扩展通信。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
28收录的工具列表 · 更新于 3 天前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | string | 是 | |
| tabId | number | 否 | 对该标签页(来自 browser_tab_list 的 ID)执行操作,但不将其设为当前标签页。省略则使用此 Agent 的当前标签页:最近导航、打开或切换到的标签页。Chrome 中不存在的 tabId 会返回 tab_not_found。 |
| session | string | 否 | 要运行的命名会话(标签页);它会成为当前标签页。使用新名称调用 browser_navigate 会在其自己的标签页中打开该会话,因此循环可以复用一个标签页,而不是打开数百个标签页。其他工具在该名称尚不存在时会报错。 |
详细介绍
PolterTab — 适用于任何 AI 的浏览器控制
让任何 AI Agent 使用你已登录的 Chrome。 PolterTab 是一个 Chrome 扩展程序和本地 MCP(Model Context Protocol)服务。
Chrome 扩展程序 + Model Context Protocol(MCP)服务。AI 可以在你已经使用的浏览器中操作:使用你的真实个人资料、Cookie 和已登录会话。安装扩展程序,将 MCP 客户端指向该服务即可——它兼容 Claude Desktop、Cursor 或任何兼容 MCP 的 Agent。
为什么选择 PolterTab
- 你的真实个人资料 — 在你已登录的 Chrome 中运行。无需使用退出了所有账号的全新无头浏览器。
- 无需复杂设置 — 从 Chrome 应用商店添加扩展程序,再添加一条 MCP 配置即可。
- 适用于任何 AI 工具 — 任何兼容 MCP 的客户端都可以连接。
工作原理
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 profile
多个 Agent 可以同时指向同一个浏览器——最先占用 WebSocket 端口的服务会为扩展程序提供服务,其余服务则通过它进行代理。参见同时运行多个 Agent。
哪些内容会离开你的设备
PolterTab 完全在你的设备上运行。它没有 PolterTab 服务、账号或遥测功能。
- MCP 服务在本地运行。 它通过 stdio 与 AI 客户端通信,扩展程序使用的 WebSocket 仅在本机监听(默认为
127.0.0.1和::1,端口 7822)。该服务会拒绝来自网页的连接——浏览器连接必须来自 Chrome 扩展程序(chrome-extension://)源——而且只有 Chrome 应用商店版本(自动受信任)或你已配对的扩展程序,才能保持该连接、更改上传设置或读取日志。(架构) - 页面内容只会发送到你的 Agent 指定的位置。 PolterTab 会将读取或抓取的内容返回给 MCP 客户端;客户端会将其转发给你配置的模型提供商。PolterTab 本身不会将页面内容传送到任何地方。
- 默认禁用文件上传,直到你允许某个文件夹。 在你使用
poltertab uploads allow <dir>添加文件夹之前,browser_upload_file会拒绝所有路径;敏感位置始终会被屏蔽,无法加入允许列表。运行poltertab uploads查看当前列表。 - npm 溯源信息。 发布时使用
npm publish --provenance,因此 npm 会记录从已发布压缩包到生成该压缩包的 GitHub Actions 构建的签名且可验证的关联信息。 - 一个例外——版本检查。 运行中的服务每天会发起一次外部请求,以检查 npm 版本(
registry.npmjs.org);poltertab setup还会一次性查询 Chrome 应用商店版本(clients2.google.com)。这两项检查只会比较版本,以便在服务与扩展程序版本不匹配时发出提醒——不会发送页面内容或个人数据。设置POLTERTAB_NO_UPDATE_CHECK=1即可关闭这两项检查。
从 1.12.0 开始, 只有 Chrome 应用商店扩展程序或你已配对的扩展程序才能控制浏览器;未打包安装的扩展程序可通过
poltertab uploads pair配对一次。
如需报告漏洞,请参阅 SECURITY.md。
设置
需要 Node 18 或更高版本。
1. 运行安装程序
npm install -g poltertab
poltertab setup
向导会询问要为所有项目安装,还是仅为当前项目安装, 然后会完成以下三项设置:
browser-navigation-strategy技能,让 Agent 知道如何操控会反抗的浏览器CLAUDE.md章节,让它知道这些工具的存在- 通过
claude mcp add注册 MCP 服务
可以放心重新运行——每个步骤都会检测是否已执行过,因此升级或再次运行 都不会重复添加内容。
不想全局安装?npx poltertab setup 可以完成相同的操作。
2. 安装 Chrome 扩展程序
从 Chrome 网上应用店**添加到 Chrome**。Chrome 会自动让通过应用店安装的扩展程序保持最新, 因此,不开发扩展程序的用户都应通过此方式安装。
然后重启 AI 工具,以便加载新工具。
或者解压加载扩展程序(开发者/离线路径)
修改扩展程序或无法访问网上应用店时,请使用此方式。 解压加载的扩展程序不会自动更新;你需要自行从较新的 zip 文件重新加载。
- 下载最新的发布 zip 文件,并将其解压到单独的文件夹中(例如,运行
unzip poltertab-extension-vX.zip -d poltertab-extension,或在 macOS 上双击——系统会自动创建文件夹) - 打开 Chrome →
chrome://extensions/ - 启用开发者模式(右上角的开关)
- 点击加载已解压的扩展程序,并选择该文件夹(其中包含
manifest.json) - 配对一次,让它能够控制浏览器:运行
poltertab uploads pair,然后输入扩展程序显示的 4 位代码。(通过应用店安装的扩展程序会自动受信任,无需此步骤。)
其他 MCP 客户端
此向导面向 Claude Code。其他任何 MCP 客户端也都可以使用——将
poltertab 注册为 stdio 命令:
{
"mcpServers": {
"poltertab": {
"command": "poltertab"
}
}
}
使用 Cursor? 一键安装(添加上述 stdio 条目):
如果从克隆仓库运行,而不是从 npm 运行,请让客户端直接指向该文件:
{"command": "node", "args": ["/absolute/path/to/poltertab/mcp-server/index.js"]}
支持的 MCP 工具
服务向 LLM 提供 28 个工具(均以 browser_ 为前缀)。所有面向页面的工具
还可接受可选的 tabId 和 session,以指定目标标签页——两者都省略时,
PolterTab 会使用 Agent 当前的标签页:即最近导航到、打开或切换到的标签页。
首次调用 browser_navigate(以及每个新会话中首次调用
browser_navigate)时,都会打开一个新标签页,而不是接管你正在查看的标签页。
页面操作
| 操作 | 参数 | 描述 |
|---|---|---|
browser_navigate | url(必填) | 导航到某个 URL |
browser_click | selector(必填) | 点击元素(CSS/XPath/文本) |
browser_fill | selector、value(必填)、submit | 填写输入字段,可选择提交其表单 |
browser_hover | selector(必填) | 将鼠标悬停在元素上 |
browser_scroll | direction(必填)、amount、selector | 滚动页面或元素——up/down/left/right/top/bottom |
browser_smart_scroll | direction(必填)、amount | 滚动,然后等待 2s,让延迟加载的网络数据到达 |
阅读页面
这里的每个工具都接受 output_file,可将负载写入
~/.poltertab/downloads/,并且只返回摘要:行数、字段名称、填充率和两条示例记录。数据量超过一瞥所需时,请使用它;原始负载绝不应默认进入上下文窗口。
| 操作 | 参数 | 说明 |
|---|---|---|
browser_extract | record, fields (required), anchor, max_text, probe, output_file | 提取重复记录,并按记录分组字段——见下文 |
browser_extract_all | url_template, record, fields (required), key, limit, offset, start_page, max_pages, fill_tolerance, output_file | 在一次调用中翻页并提取,无需每页与模型往返一次 |
browser_scrape | selector, attribute, multiple, fields, max_text, output_file | 抓取指定元素或整个页面。attribute 除任何属性名称外,也接受 text;href/src 会返回绝对路径。fields 用于选择完整页面抓取要返回的部分——["meta","jsonld"] 可获取 og: 标签和 schema.org 数据块,而无需返回 50KB 的正文文本 |
browser_snapshot | interactive_only, max_nodes, max_depth, output_file | 带引用标签的可见节点和交互节点树,最多 400 个节点 |
browser_get_text | selector (required), max_text, output_file | 一个元素的文本内容。如果文本被截断,会在结果中标记 |
browser_get_title | — | 页面标题和 URL |
browser_get_url | — | 页面 URL 和标题 |
browser_screenshot | — | 当前可见标签页的 base64 PNG(会聚焦该标签页) |
提取记录
browser_scrape 返回一个扁平列表,因此无法追踪每个值属于哪条记录。只有一个元素时这没问题,但有九十个元素时就很危险:只要有一个字段是可选的,按名称和电话号码进行扁平抓取,就会让后续所有电话号码上移一行,并导致页面其余数据错配。
browser_extract 接受一个记录容器,并在容器内部解析每个字段:
{
"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"
}
- 缺失字段会返回
null。 绝不会导致数据错位。 sel相对于记录根节点;省略它(或使用".")即表示根节点本身。get可以是text(默认值)、href、src或任何属性名称。anchor指定一个真实记录必定包含的字段。缺少该字段的记录——例如列表最后一页用于填充版面的占位卡片——会被丢弃,并计入dropped,而不会作为全是 null 的行输出。
结果会报告执行情况,因此空列不会被误认为有效答案:
{ "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"] }
该警告表示启用了放宽探测。任何返回结果完全为空的字段都会在整个页面范围内重新检查,因为看起来像记录的容器往往不包含某些字段所在的同级元素;而“0 个社交账号”读起来像是“这些人没有社交账号”,而不是“你的选择器范围有误”。
browser_extract_all 接受相同的规范,外加一个包含 {page} 的 url_template,并自行执行循环。它会按 key 去重,并在以下任一条件首次出现时停止:limit_reached、empty_page、duplicate_page(某页的记录与更早页面重复——这是网站忽略无法识别的页面大小参数时的表现)、fill_rate_deviation(某页的填充率相较于第一页的基准骤降,这也是在第 1 页学到的规范悄然退化到第 10 页时的表现),或 max_pages。它始终会报告触发的条件,并返回截至该时点收集的所有内容,因此停止不会让你白跑一趟。
.csv 和 .jsonl 输出文件会以对应格式写入。
网络捕获
对于 DOM 经过混淆或虚拟化的网站,应读取驱动 UI 的 JSON,而不是抓取可能不存在的元素。
| 操作 | 参数 | 说明 |
|---|---|---|
browser_set_intercept_patterns | patterns(必填) | 要捕获的 URL 子字符串,例如 ["graphql", "/api/v1/"]。默认为 ["graphql", "/api/", "voyager", "feed"] |
browser_get_network_state | clear、output_file | 捕获该标签页的原始 JSON。output_file 会将内容写入 ~/.poltertab/downloads/,并且只返回路径,避免大型负载占用上下文窗口 |
会话
会话是一个有名称的标签页。为每个 Agent 分配自己的会话,可避免多个 Agent 同时操作一个标签页;关闭的标签页会在下次使用时根据存储的 URL 重新创建。
| 操作 | 参数 | 说明 |
|---|---|---|
browser_session_create | name(必填)、url、tabId | 创建会话、接管已打开的标签页,或将会话绑定到当前标签页 |
browser_session_switch | name(必填) | 激活会话;如果其标签页已关闭,则恢复该标签页 |
browser_session_list | — | 列出所有会话,每个会话的状态为 alive、recoverable 或 expired |
browser_session_close | name(必填)、force | 关闭会话及其标签页。被接管的标签页会返回 needs_user_permission;征求用户同意后,使用 force: true 重试。 |
browser_session_context | — | 当前标签页的 ID、URL、标题和会话,或 closedTabId |
标签页
一个 Agent 可以操作多个标签页。每个 Agent(以及每个辅助服务)都有自己的当前标签页;未指定 tabId/session 的面向页面工具会在该标签页上执行。指定 tabId 时,工具会操作对应的标签页,而不会切换当前标签页;如果 Chrome 中不存在该 tabId,任何接受该参数的工具都会返回 tab_not_found。如果当前标签页已关闭,未指定目标的调用会显示一条指出已关闭标签页的消息并失败,而不会悄悄转而操作其他标签页。
| 操作 | 参数 | 说明 |
|---|---|---|
browser_tab_new | url、session、background | 打开一个标签页并将其设为当前标签页 |
browser_tab_list | all | 列出 PolterTab 的标签页(或所有标签页),并标记当前标签页 |
browser_tab_switch | tabId 或 session、focus | 将另一个标签页设为当前标签页。如果 Chrome 中不存在该标签页,则返回 tab_not_found。 |
browser_tab_close | tabId 或 session、force | 关闭一个标签页(默认为当前标签页)。由 PolterTab 以外的方式打开的标签页(包括通过 browser_session_create({tabId}) 接管的标签页)会返回 needs_user_permission——征求用户同意后,使用 force: true 重试。如果 Chrome 中不存在该标签页,则返回 tab_not_found。 |
文件上传
| 操作 | 参数 | 说明 |
|---|---|---|
browser_upload_file | selector(必填)、paths(必填)、method("picker" 或 "drop")、tabId、session | 将本地文件上传到页面。method: "picker"(默认)会点击 selector,并通过 CDP 拦截操作系统的文件选择器——适用于任何 <input type="file"> 或会打开文件选择器的按钮。method: "drop" 会触发 DataTransfer 拖放事件——适用于拖放区域。路径必须位于 ~/.poltertab/config.json 中 upload_allowed_paths 列出的目录内。使用文件选择器上传期间会短暂显示调试横幅(CDP 要求)。 |
配置——使用 browser_upload_file 前,将 upload_allowed_paths 添加到 ~/.poltertab/config.json:
{
"upload_allowed_paths": ["/Users/you/Documents", "/Users/you/Downloads"]
}
无论是否在允许列表中,以下路径始终被阻止:~/.ssh、~/.aws、~/.gnupg、~/.config、/etc、/private/etc、/System、/private/System、/Library、/private/Library、/proc、主目录中的点文件。注意:/tmp(及其 macOS 真实路径 /private/tmp)不会被阻止,可以正常添加到 upload_allowed_paths 中。
站点记忆
| 操作 | 参数 | 描述 |
|---|---|---|
browser_get_site_memory | hostname(或 domain) | 记录的域名障碍及解决方法 |
browser_save_site_memory | hostname(或 domain)、obstacle、solution | 记录网站遇到的问题及解决方法 |
选择器解析
选择器按以下顺序解析:
- 快照引用 —
@e12,由browser_snapshot返回。解析为快照所描述的确切节点,优先于生成的类名链。 - CSS 选择器 —
#id、.class、div > span - XPath —
//div[@class="foo"] - 文本匹配 — 元素的精确文本内容
如果以上四种方式都未匹配到,则会重试,并穿透 Shadow DOM 根节点(开放根节点,深度为一层)和搜索顶层同源 iframe。不支持嵌套 iframe(iframe 内含 iframe)和跨域 iframe;在这些情况下,工具会返回明确的错误。
故障排除与选项
同时运行多个 Agent:
第一个绑定端口 7822 的 MCP 服务会成为主服务,并负责与扩展程序建立连接。之后启动的服务发现端口已被占用后,会切换为次级模式,通过主服务代理命令,因此多个 Agent 无需额外配置即可共享一个浏览器。主服务会为每个次级服务的命令附上其 ID,扩展程序会为每个 Agent 分别保留当前标签页,因此各 Agent 都有自己的标签页。最多接受 POLTERTAB_MAX_SECONDARIES 个 v2 次级服务(默认值为 50);如果主服务退出,其中一个次级服务会晋升并接替它。扩展程序弹出窗口会列出所有已连接的 Agent。
一个浏览器可供多少个 Agent 共享:
最多可由 50 个 v2 次级服务(每个进程一个)和主服务共享一个浏览器。通过 POLTERTAB_MAX_SECONDARIES 提高或降低上限。如果某个 Agent 闲置达到 POLTERTAB_IDLE_EVICTION_MS(默认 5 分钟),则会以关闭代码 4001 将其逐出,以释放名额;它下次发送命令时会自动重新连接,因此 Agent 不会察觉此次逐出。主版本与主服务不匹配的 Agent 会以关闭代码 4002 被拒绝——请参阅下文的升级路径。
命令队列:
主服务会限制同时发送给扩展程序的命令数量(POLTERTAB_MAX_CONCURRENCY,默认值为 6)。达到上限时,v2 次级服务的命令会按次级服务分别排队(最多 POLTERTAB_QUEUE_DEPTH 个名额,默认值为 3)。排队命令会立即收到 {"type":"queued","id":"...","position":N} 确认,让调用方知道命令正在等待处理,而非已丢失。v2 次级服务在收到此确认时,以及命令离开队列时收到 {"type":"dispatched","id":"..."} 后,都会重新启动 35 s 超时计时器,因此队列等待时间不会占用执行时限。如果某条命令到达时该次级服务的队列已满,则会立即以 [queue_full action=X] 拒绝。主服务在相同上限下也会为自己的直接命令排队。调用方的 35 s 时限即将耗尽时,排队命令会被丢弃,并返回 [queue_timeout action=X](预留 2 s 缓冲时间,以确保丢弃发生在调用方自身计时器超时之前)。POLTERTAB_QUEUE_TIMEOUT_MS(默认值为 60 s)是针对始终未出队的队列命令设置的独立垃圾回收兜底机制,不会延长队列等待时间。
/status 端点:
GET http://127.0.0.1:7822/status 会返回主服务队列状态、已连接 Agent 和等待时间百分位数(p50/p95/p99)的 JSON 快照。该端点仅允许回环地址(127.0.0.1、::1 或 ::ffff:127.0.0.1)的调用方访问,且请求的 Host 标头必须是 localhost 或回环地址;其他请求均会收到 404。
扩展重连:
桥接服务重启或 WebSocket 断开时,Chrome 扩展会自动重新连接。断开后会立即尝试连接,然后按 1 s、2 s、4 s……的间隔逐渐延长,最长为 30 s;如果服务工作线程在计划执行过程中被挂起,闹钟会唤醒它。重连期间,命令会返回:
[extension_reconnecting port=7822 elapsed=Xs]— 扩展已断开,正在重新连接。请稍后重试。[extension_never_connected port=7822]— 扩展从未连接到此服务实例。请检查扩展是否已安装、启用,并且指向正确的端口。
关闭代码:
| 代码 | 含义 | Agent 行为 |
|---|---|---|
| 4001 | 空闲时逐出 — Agent 在 POLTERTAB_IDLE_EVICTION_MS 指定的时间内没有活动 | 次级实例会在下次收到命令时延迟重新拨号;无需采取操作 |
| 4002 | 版本不匹配 — 次级实例的主版本与主实例不兼容 | 次级实例会停止重试,直至重启;请更新 npm 包,确保每个会话运行相同的主版本 |
升级路径(1.x → 1.8):
PolterTab 1.8 引入了版本握手。主版本与主实例不匹配的次级实例会收到关闭代码 4002 并停止重试;更新 npm 包(确保所有会话运行相同的主版本)即可解决问题。同时也应更新 Chrome 扩展,以获得 PR-4 的重连行为。升级前请运行 poltertab doctor 检查版本是否不一致。尚未升级的 1.7.x 次级实例不会收到 4002(它不支持握手),并会继续作为未标记版本的旧版次级实例运行;它会绕过命令队列,且永远不会因空闲而被逐出。
环境变量:
| 变量 | 默认值 | 说明 |
|---|---|---|
POLTERTAB_MAX_SECONDARIES | 50 | 最多接受的 v2 次级实例数量 |
POLTERTAB_IDLE_EVICTION_MS | 300000 (5 min) | v2 次级实例被以 4001 逐出前的空闲时长 |
POLTERTAB_MAX_CONCURRENCY | 6 | 扩展命令并发限制 |
POLTERTAB_QUEUE_DEPTH | 3 | 每个次级实例在返回 [queue_full] 前的队列深度 |
POLTERTAB_QUEUE_TIMEOUT_MS | 60000 (60 s) | 卡住的排队命令的 GC 兜底超时(不会延长队列等待时间) |
POLTERTAB_COMMAND_TIMEOUT_MS | 35000 (35 s) | 每条命令的时间预算。必须大于扩展的 30 s 导航等待时间加 500 ms 稳定时间;仅针对测试调整 |
更改 WebSocket 端口:
仅当占用 7822 的是 并非 PolterTab 的其他程序时,才需要更改端口——PolterTab 服务按设计共享该端口(见上文)。两端都必须更改:
-
点击 Chrome 中的扩展图标 -> 设置。
-
将 WebSocket 端口更改为一个空闲端口(例如
7824),然后点击 保存 — 扩展会立即重新连接,无需重新加载。 -
更新 MCP 服务配置,传入新端口。
- 使用环境变量:
MCP_BROWSER_WS_PORT=7824 node index.js - 使用 CLI 参数:
node index.js --port 7824 - 在 Claude Desktop 配置中:
"args": ["/absolute/path/to/poltertab/mcp-server/index.js", "--port", "7824"] - 使用环境变量:
DevTools 打开时选择器上传失败:
Chrome 每个标签页一次只能启用一个调试器。当目标标签页打开 DevTools 时,browser_upload_file(方法 "picker")会失败并显示“Another debugger is already attached”。运行选择器上传前,请关闭该标签页上的 DevTools。
添加 debugger 权限后需要重新批准扩展:
添加 debugger 权限后加载或重新加载扩展文件夹时,Chrome 会提示你重新批准 — 在 chrome://extensions/ 的警告对话框中点击 保留 或 允许 之前,扩展会处于停用状态。以解压方式加载的扩展不会自动更新,因此只有在你明确重新加载或更新扩展文件夹时才会发生这种情况。
扩展未连接:
- 确保 MCP 服务正在运行,并且指向正确的端口。
- 从
chrome://extensions/重新加载扩展
命令超时 / 受限页面:
- 确保 Chrome 中有一个活动标签页。
- Chrome 扩展无法与
chrome://URL、Chrome 网上应用店或某些受限页面交互。在这些情况下,MCP 服务会向 Agent 清晰地报告“无法与此页面交互”。
项目结构
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.md
保持更新
poltertab doctor
此命令会报告已安装的版本、npm 是否有更新版本,以及——最重要的是——两个部分的版本是否一致:
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
运行 npm update -g poltertab 时,npm 包会更新。从 Chrome 网上应用店安装的扩展会像其他网上应用店扩展一样自动更新。已加载的解压扩展绝不会自行更新——Chrome 只会自动更新来自网上应用店的扩展。因此,真正需要关注的问题不是缺少更新,而是版本不一致:较新的服务发送了较旧扩展尚未实现的命令(或相反,Chrome 已更新扩展,但你还没有运行 npm update),这会导致元素莫名其妙地无法找到。
两个部分使用同一个版本号,因此版本不一致一目了然。扩展会在连接时报告自己的版本;服务会进行比较、记录日志,并在扩展弹出窗口中显示结果。如果任一部分版本落后,会话中的第一个工具响应会提示一次,让你及时得知,无需特意记得去检查。
更新检查每天最多访问一次 registry.npmjs.org,结果会缓存在 ~/.poltertab/update-check.json 中,且绝不会阻塞任何操作。设置 POLTERTAB_NO_UPDATE_CHECK=1 可关闭更新检查——版本不一致检测在本地进行,无论是否关闭更新检查都会继续工作。
发现版本不一致时,poltertab doctor 会以非零状态码退出,以便 CI 或 Shell 提示符据此采取操作。
数据存储位置
服务积累的所有内容都不会存储在包内:
~/.poltertab/
├── navigation_memory/ # Per-domain obstacles and fixes (browser_save_site_memory)
└── downloads/ # output_file payloads from any read tool
对于全局 npm 安装而言,这一点很重要。包位于 node_modules/ 中,因此写在代码旁边的任何内容都会在下次运行 npm update -g 时被删除——每次升级后,站点记忆都会像失忆一样,而抓取的负载则会被埋在你永远不会去找的地方。
设置 POLTERTAB_HOME 可迁移整个目录树。如果你正在从将记忆保存在 mcp-server/navigation_memory/ 的版本升级,那么首次启动时会将其复制过来;如果目标位置已有笔记,则始终以现有笔记为准。
发布
发布由 GitHub Releases 驱动。npm 发布渠道由版本字符串决定,因此只需确保 Git 标签正确:
| 版本 | npm 标签 | 安装方式 |
|---|---|---|
1.2.0 | latest | npm install -g poltertab |
1.2.0-rc.1 | rc | npm install -g poltertab@rc |
1.2.0-beta.1 | beta | npm install -g poltertab@beta |
1.2.0-alpha.1 | alpha | npm install -g poltertab@alpha |
发布一个版本:
npm version 1.2.0-beta.1 # bumps package.json, syncs the manifest, tags
git push --follow-tags
gh release create v1.2.0-beta.1 --generate-notes --prerelease
npm version 会运行 scripts/sync-manifest-version.js,确保 chrome-extension/manifest.json 中的版本同步。两个文件不能使用相同的字符串——Chrome 会拒绝带有预发布标识符的清单版本——因此,数字基础版本写入 version,完整字符串写入 version_name。
随后发布该版本会运行工作流:执行测试、检查版本一致性、在解析出的标签上运行 npm publish --provenance,并构建扩展 ZIP 文件并将其附加到发布中。
检查程序会拒绝发布,而不会自行猜测。如果标签与 package.json 不一致、清单文件版本落后,或稳定版本被标记为预发布版本,整个流程都会失败——最后一种情况之所以会失败,是因为否则该版本会进入 latest,并成为所有人的默认安装版本。
使用工作流的手动触发器(dry_run: true),即可演练整个流程而不实际发布。
测试
npm install
npm test
无需框架和浏览器。共有两个测试套件:
mcp-server/test/run.js— 内容脚本注入幂等性、穿透 Shadow DOM、跨框架搜索、标签页导航加载竞态,以及 MCP 服务端到端测试。它会在端口7931上针对一个虚拟扩展启动真实的服务进程,因此不会干扰已在7822上运行的 PolterTab。test/setup.test.js— 安装程序:作用域解析、技能安装,以及CLAUDE.md幂等性。每个用例都会在一次性临时目录中运行,因此泄漏的写入不会影响你真实的~/.claude。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。