让 AI Agent 去操作一个只认浏览器登录态的内网系统

举报
岚枫 发表于 2026/10/10 21:18:22 2026/10/10
【摘要】 最近我在把「华为云开发者成长中心」接给 AI Agent,用的开源项目是 HWC Growth Center MCP Extension——GitCode 上的 HotWater23/develope

最近我在把「华为云开发者成长中心」接给 AI Agent,用的开源项目是 HWC Growth Center MCP Extension——GitCode 上的 HotWater23/developer-grow-mcp-extension,作者是 HotWater23,MIT 许可。它把签到、积分、任务这些能力包装成 MCP 工具,架构是 Chrome 扩展加一个 Node.js Bridge。代码不算长,但我在把它跑起来、拆开看它为什么这么设计的路上,踩的坑不少。

下面这些坑,一部分来自项目作者在仓库文档里记录的探索过程,一部分是我自己实测出来的。哪些是我自己踩的,我尽量标清楚。

目标系统的接口在内网,只认 developer.huaweicloud.com 这个域的浏览器会话 Cookie。我一开始没把这当回事。一个内部服务而已,我有 IAM Token,账号也是自己的,该拿的凭证都拿到了,直连一个接口能有多难。结果在这上面反复栽跟头,一路试下来才明白,问题不在"能不能拿到凭证",而在"这套凭证在哪儿执行才生效"。

拿 Token 直连,换来一句 403

这段弯路是项目作者在仓库文档里记下来的,我照着复现了一遍。

先用 IAM Token 换到 X-Subject-Token,拿着它直连签到接口,返回的是 403 "not login"。再换一条路:把浏览器里的 Cookie 整个复制出来,塞进 Python 的 requests 里重放,能带的 header 都带上——一样失败。

作者当时的结论是:这套登录态不是靠 Token 验的,是绑在浏览器会话上的。Cookie 是 httponly 的,还跟 IP 绑定,复制到另一个进程、另一个客户端里就失效了。你在浏览器里登录了,不代表你复制出去的那串字符串还带着"登录"这个状态。唯一可行的路径,是在浏览器上下文里发请求——让请求从那个已经登录的页面里出去。

这一条定下来,整个架构就被锁死了:不能是纯后端服务,必须有个浏览器侧的组件。

中间为什么夹了一个 Node.js Bridge

浏览器侧的组件是 Chrome 扩展。但 Chrome MV3 的 Service Worker 有个硬限制:它没法创建 WebSocket 服务器。也就是说,扩展不能自己监听一个端口,等外面来连它。

那就只能反过来:由扩展主动往外连。可是外面的 CLI 或者 MCP 客户端又只认 stdio,不会开端口等扩展连。两边都对不上。

所以中间必须有个外部 Node 进程当中继,把 stdio 上的 JSON-RPC 和 WebSocket 互转。外面跟 Node 进程用 stdio 通信,Node 进程再通过 WebSocket 把请求转发给扩展。它干的活儿就是翻译,把一侧的协议转成另一侧的协议。

Bridge 我做了点限制:只监听 127.0.0.1,要求一个至少 32 字符的配对令牌,同一时间只允许一个扩展连上来。扩展没连上的时候,请求先排队等着,不会直接丢。

CSRF 这个坑,只在浏览器里才露头

有个接口的 CSRF 令牌,我一开始想从 Node 侧直接拿。请求 https://developer.huaweicloud.com/api/get-ainfo,返回 HTTP 200,看起来一切正常,但响应头里就是没有 csrf 这个字段。

换到浏览器上下文里请求同一个地址,csrf 响应头才出现。

这也是为什么绕不开浏览器——不是接口不通,是关键的响应头只在特定上下文里才会带。这个令牌有效期大概 15 分钟,过期了会自动刷新,所以也不能拿一次存起来一直用。

Chrome 把 --load-extension 关了

调试的时候我想自动化一点,用命令行把解压扩展带起来,省得每次手动点。写了个 --load-extension 启动参数,发现 Chrome 154 已经禁用这个开关了。

我不死心,又加上 --disable-features=DisableLoadExtensionCommandLineSwitch,还是不行。查下来就是这条路被堵死,命令行没法把解压扩展带起来,只能手动去加载。

自动化到这里就断了。每次调试都得手动点一遍加载,反复重装扩展的时候尤其烦。

顺带记一个细节:解压扩展的 ID 是由扩展目录的绝对路径推出来的,路径不变,ID 就不变。这对我挺重要,后面不少配置都依赖这个固定 ID。

令牌明文躺在 LevelDB 里

配对令牌存在 chrome.storage.local 里。我去磁盘上翻,发现在 Chrome 用户目录的 Local Extension Settings/<扩展ID>/ 下,一个 LevelDB 里,键是 bridgeToken,值是 64 位十六进制。明文,没有任何加密。

看到这个的时候我愣了几秒。倒不是发现了什么漏洞,就是它提醒我,这类东西的边界得自己先想清楚,不能想当然。本机、明文、只有本机能读到,这套假设成不成立,得看你怎么用。

没有浏览器的时候,怎么验证

开发过程中有一大块时间是没有浏览器可用的,或者不想每次都开浏览器。故障到底出在 CLI、Bridge、还是扩展?很难判断。

我的做法是写了个"模拟扩展"——一个假的扩展,忠实实现扩展侧的协议:先注册鉴权,然后应答 initialize、tools/list、tools/call 这几个方法。用的东西尽量少,Node 22 内置的 WebSocket 就够了,没引第三方库。

拿它把 CLI → Bridge → WebSocket → MCP 协议 → 工具 → 回包 整条链跑通之后,心里就有底了:协议、转发、回包这些环节本身没问题。这样一来,剩下的故障范围就被逼到"只剩浏览器侧"。再去查问题,就只需要盯着扩展一个地方,不用在四五个组件之间来回猜。

这个方法后来救了我不少次。能把问题范围一刀切干净,比在一堆组件里瞎猜强太多。

那个最误导人的坑:端口

我文档里写过一条排错建议:端口被占的话,设 MCP_BRIDGE_PORT 换个端口。

这条是错的,我实测之后才发现。

Bridge 侧确实读这个环境变量,设了之后它会挪到新端口。但扩展侧没跟着动——background.js 里 ws://127.0.0.1:9876 是写死的,options 页里也根本没有端口这一项。结果就是 Bridge 换了新端口,扩展还在敲旧端口,两边永远握不上手。

最坑的是报错。它显示的是"扩展未连接",而不是"端口不对"。你顺着这条线索查,会一直以为扩展没起来,去重启扩展、重装扩展、看扩展日志,查半天什么也查不到。其实扩展好好的,只是它敲的那扇门,Bridge 早就搬走了。

我当时就是被这句"扩展未连接"带着绕了很久,直到想起来去看 background.js 里那个写死的端口,才对上号。

两条排错建议,是从上游 README 抄的

上面那个端口建议,还有另一条,我后来发现都不是我自己验证过的——是从上游项目的 README 里抄过来的。

二手信息看着最像样,也最容易原样继承错误。我抄的时候觉得"README 上写的,应该没问题",结果把别人没验证过的东西当成了自己的结论,又写进了自己的文档里。

现在回看,我文档里凡是"据说""一般""应该"开头的话,都得重新跑一遍才算数。

还没解决的那件事

端口这件事我到现在也没改上游代码。MCP_BRIDGE_PORT 那个环境变量还挂在那儿,谁设谁踩。我知道该怎么改——让扩展侧从 Bridge 拿到实际端口,或者干脆让 Bridge 别读环境变量——但一直没动手。

它不影响主流程,只在端口冲突的时候冒出来咬一口。我就先这么放着了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。