1. 浏览器里跑通 MCPAI-Extension 与 WXT 到底解决了什么问题AI-Extension 是 OpenTiny 团队基于 WXT 框架开发的一款智能浏览器扩展核心思路是把 MCPModel Context Protocol协议搬进浏览器让 AI 助手不再只是“读网页”而是能真正“点按钮、填表单、跳页面”。如果你之前用过 Cursor、Claude、Coze 这类工具会发现它们对网页的感知基本停留在文本或截图层面遇到动态渲染的 Vue/React 组件、需要登录态的内部系统就很容易卡住。AI-Extension 的价值就在于它作为浏览器扩展运行天然复用你的 Cookie、缓存和登录态同时通过无障碍树解析和视觉模型拿到页面结构快照再把 click、fill、select 这些操作封装成 MCP 工具暴露给 AI。WXT 是这套方案的工程底座。它是一个面向现代浏览器扩展的开发框架支持 Manifest V3、多浏览器构建、热更新和 TypeScript 开箱即用。相比手写 manifest.json 和 webpack 配置WXT 的目录约定和自动导入能省掉大量样板代码。我这次的目标很明确用 WXT 搭一个最小扩展骨架把 MCP 客户端接进去然后把请求端点统一改到 TaoToken 的 API 通道最后在本地 Chrome 里跑通一次完整的 MCP 调用链路。适合谁适合想自己动手做浏览器侧 AI 自动化、又不想从零造轮子的前端或全栈同学。整个链路可以拆成四层WXT 扩展骨架负责生命周期和页面注入MCP 客户端负责和模型侧通信TaoToken 统一 Key/API 通道负责鉴权和模型路由浏览器侧工具层负责把 DOM 操作注册成 MCP 工具。下面按可跟做的顺序展开每一步都给到可复制的配置和命令。2. 前置准备TaoToken 统一 Key 与 API 通道配置在写代码之前先把模型侧的入口准备好。TaoToken 在这里扮演的是统一 Key/API 通道的角色你不需要在扩展里硬编码多个厂商的 Key而是通过一个端点做模型路由。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。第一步是拿到 API Key。进入控制台后创建密钥建议按项目维度命名比如wxt-mcp-extension方便后续轮换。创建完成后你会得到一串以sk-开头的 Key先复制到本地临时文件后面写进.env时用。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。第二步是确认模型 ID。不同任务对模型能力要求不同MCP 工具调用场景建议选支持 function calling 的模型。你可以在模型对话页先做一次简单验证地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 输入一句“请用 JSON 返回一个 click 工具的定义”看返回结构是否符合预期。第三步是理解鉴权方式。TaoToken 的 API 走标准 Bearer Token请求头形如Authorization: Bearer sk-xxxx。在浏览器扩展里这个 Key 不能直接写进前端代码因为扩展包是可以被解压查看的。推荐做法是开发阶段用.env注入构建时通过 WXT 的import.meta.env读取生产环境则应该走你自己的后端做代理扩展只持有短期令牌。这一点在后面的配置片段里会体现。如果你后续要做长期编码或 Agent 类任务可以了解 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它更适合持续性的开发场景。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 遇到参数疑问优先查这里。3. WXT 项目骨架与 MCP 客户端可复制配置先初始化 WXT 项目。确保 Node 版本在 18 以上然后执行npx wxtlatest init ai-extension-mcp --template react-ts cd ai-extension-mcp npm installWXT 的目录约定是entrypoints/放各个入口components/放 UI 组件public/放静态资源。默认会生成entrypoints/background.ts和entrypoints/popup/。我们要加一个 content script 用来注入页面工具层再加一个 sidepanel 作为对话界面。先改wxt.config.ts声明权限和 host 权限import { defineConfig } from wxt; export default defineConfig({ manifest: { name: AI-Extension MCP, permissions: [storage, scripting, activeTab, sidePanel], host_permissions: [all_urls], side_panel: { default_path: sidepanel.html, }, }, });接着配置环境变量。在项目根目录建.envVITE_TAOTOKEN_BASE_URLhttps://taotoken.net/api VITE_TAOTOKEN_API_KEYsk-你的Key VITE_MCP_MODEL_ID你的模型ID注意.env要加进.gitignore别提交。然后在entrypoints/background.ts里写 MCP 客户端的最小实现。这里用 fetch 直接调 TaoToken 的 chat completions 端点把工具定义传进去export default defineBackground(() { const BASE_URL import.meta.env.VITE_TAOTOKEN_BASE_URL; const API_KEY import.meta.env.VITE_TAOTOKEN_API_KEY; const MODEL_ID import.meta.env.VITE_MCP_MODEL_ID; const tools [ { type: function, function: { name: click_element, description: 点击页面上的元素, parameters: { type: object, properties: { selector: { type: string, description: CSS 选择器 }, }, required: [selector], }, }, }, { type: function, function: { name: fill_input, description: 向输入框填写内容, parameters: { type: object, properties: { selector: { type: string }, value: { type: string }, }, required: [selector, value], }, }, }, ]; chrome.runtime.onMessage.addListener(async (msg, _sender, sendResponse) { if (msg.type ! MCP_CALL) return; const res await fetch(${BASE_URL}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY}, }, body: JSON.stringify({ model: MODEL_ID, messages: msg.messages, tools, tool_choice: auto, }), }); const data await res.json(); sendResponse(data); return true; }); });这段代码的关键点tools数组就是 MCP 工具在模型侧的声明模型返回tool_calls后由 content script 执行真实 DOM 操作。Base URL、Key、Model ID 三件套全部来自环境变量符合统一通道的接入方式。再写 content script负责执行工具调用export default defineContentScript({ matches: [all_urls], main() { chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) { if (msg.type ! EXEC_TOOL) return; const { name, args } msg; try { if (name click_element) { const el document.querySelector(args.selector); if (!el) throw new Error(元素未找到); (el as HTMLElement).click(); sendResponse({ ok: true }); } else if (name fill_input) { const el document.querySelector(args.selector) as HTMLInputElement; if (!el) throw new Error(输入框未找到); el.value args.value; el.dispatchEvent(new Event(input, { bubbles: true })); sendResponse({ ok: true }); } } catch (e) { sendResponse({ ok: false, error: (e as Error).message }); } return true; }); }, });到这里WXT 骨架、MCP 客户端、工具执行层就齐了。接下来是加载验证。4. 加载扩展并验证一次完整 MCP 调用链路构建开发版本npm run devWXT 会输出一个.output/chrome-mv3-dev目录。打开 Chrome地址栏输入chrome://extensions右上角开启“开发者模式”点“加载已解压的扩展程序”选择.output/chrome-mv3-dev。加载成功后扩展列表里会出现 AI-Extension MCP。接着打开任意一个测试页面比如一个带登录表单的本地 HTML。点扩展图标打开 sidepanel在输入框里发一句“帮我点击 id 为 login-btn 的按钮”。sidepanel 会把消息发给 backgroundbackground 调 TaoToken 的 chat completions模型返回tool_callsbackground 再把EXEC_TOOL消息发给 content scriptcontent script 执行document.querySelector(#login-btn).click()。验证成功的标志有三个一是 sidepanel 里能看到模型返回的 tool_calls 结构二是页面上的按钮真的被点击了三是 background 的 Network 面板里能看到对https://taotoken.net/api/v1/chat/completions的 200 响应。如果这三步都通说明 MCP 调用链路在浏览器侧跑通了。如果你想更直观地看模型返回可以在 sidepanel 里加一段渲染逻辑把data.choices[0].message.tool_calls打印出来。实测下来第一次调用可能会有几百毫秒延迟属于正常范围。另外注意content script 默认运行在隔离环境拿不到页面 JS 内存里的变量如果你要读 Vue/React 状态需要把world改成MAIN但那样会牺牲一部分安全性按需选择。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节按真实报错来对照都是我在接入过程中遇到或社区里高频出现的。401 Unauthorized最常见的原因是 Key 没读到或格式不对。先检查.env里VITE_TAOTOKEN_API_KEY是否以sk-开头然后确认构建时环境变量真的被注入——WXT 只暴露VITE_前缀的变量。如果 Key 正确但仍 401检查请求头是不是写成了Authorization: sk-xxx正确格式是Bearer sk-xxx。另外 Key 被删除或过期也会 401去 API Keys 页面确认状态。local proxy failed这个报错通常出现在你本地起了代理服务、但扩展请求没走通的情况下。排查顺序是先确认VITE_TAOTOKEN_BASE_URL写的是https://taotoken.net/api而不是带路径的完整端点再确认扩展的 host_permissions 包含all_urls最后看 background 的 console 有没有跨域报错。如果是 Manifest V3fetch 在 background service worker 里发起不受页面 CORS 限制但需要确保 service worker 没被浏览器休眠中断。reading choices of undefined这个报错说明data.choices是 undefined通常是响应体不是预期的 JSON 结构。可能原因有三个一是请求打到了错误端点比如漏了/v1二是模型 ID 写错服务端返回了错误对象三是响应被拦截成了 HTML。排查方法是在 background 里先console.log(await res.text())看原始返回。确认端点、模型 ID、Key 三件套一致后这个问题基本就消失了。OAuth 相关报错如果你在扩展里接了需要 OAuth 的第三方服务可能会遇到 token 过期或 scope 不足。注意 MCP 工具调用本身不依赖 OAuth它走的是 TaoToken 的 Bearer Token。如果你看到 OAuth 报错先确认是不是把两套鉴权混在一起了。扩展侧的 OAuth 应该单独走chrome.identity和模型通道分开管理。另外提一个容易忽略的点如果你同时装了 Cline、CC Switch 或 Codex 这类工具它们的auth.json或 MCP 配置可能会和扩展的配置冲突。建议把扩展的 Base URL、Key、Model ID 三件套单独放在.env里不要和编辑器侧的配置混用。CC Switch 的配置里如果出现 MCP server 定义记得 Base URL 指向https://taotoken.net/apiKey 用同一套Model ID 保持一致这样排查时变量最少。6. 把链路固定下来后续接入与验证入口跑通一次之后建议把验证步骤固化成一个小脚本或 checklist避免每次改配置都重新摸索。我的做法是在项目里放一个scripts/verify-mcp.mjs用 Node 直接调一次 chat completions确认 Key 和模型 ID 可用再去浏览器里验证工具执行。这样能把“模型侧问题”和“扩展侧问题”分开定位。如果你要验证模型返回结构模型对话页是最快的入口https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你要管理或轮换 Key去 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入参数和端点细节以文档为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期做编码或 Agent 任务的话Coding Plan 更适合持续调用https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后说一个实操细节WXT 的npm run dev在改动 background 后会自动重载扩展但 content script 的改动有时需要手动刷新页面才生效。如果你发现工具执行没反应先刷新目标页面再看 background 的 service worker console。这个坑我踩过排查了半小时才发现是 content script 没重新注入。把这一步写进你的验证流程能省不少时间。