ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

浏览器AI扩展开发实战:Codex OAuth与WebMCP技术解析

浏览器AI扩展开发实战:Codex OAuth与WebMCP技术解析 1. 为什么我决定把 AI 助手塞进每一个标签页浏览器扩展这个赛道说实话已经卷了很多年了。从最早的广告拦截、密码管理到后来的截图标注、网页剪藏几乎每一个细分需求都被反复做过。但最近一年我明显感觉到一个变化AI 能力正在从独立应用往浏览器原生体验迁移。以前你想让 AI 帮你总结一篇文章得复制链接、切到另一个窗口、粘贴、等结果、再切回来。这个流程里光是切换上下文就消耗掉了大部分耐心。EO2Weave 这个扩展上架 Chrome 应用商店本质上解决的就是这个上下文切换的问题。它的定位很直接——让 AI 助手常驻在每一个标签页里你不需要离开当前页面就能对页面内容做总结、问答、提取、改写。听起来像是又一个AI 侧边栏但真正让我愿意花时间研究它的是它背后用到的两个技术点Codex OAuth和WebMCP。前者决定了它怎么安全地接入 AI 能力后者决定了它怎么和网页本身交互。这两个东西组合起来才是它区别于普通套壳侧边栏的关键。这篇文章适合几类人看一是想自己动手做一个浏览器 AI 扩展的开发者二是想搞清楚 Codex OAuth 和 WebMCP 到底怎么落地的人三是单纯好奇AI 住进标签页这件事技术上是怎么实现的从业者。我会从整体设计思路讲起然后拆核心细节、实操流程、踩坑记录尽量把每一步的为什么讲透。你不需要有很深的扩展开发经验但至少要写过一点 JavaScript知道 Chrome 扩展的基本结构manifest、content script、background service worker 这些概念。先说结论这个扩展的技术选型里最值得学的不是 UI而是它怎么处理AI 能力接入和页面上下文获取这两件事。前者用 OAuth 解决了密钥安全问题后者用 WebMCP 解决了页面数据读取的标准化问题。下面我逐层拆开讲。2. 整体设计思路为什么是常驻标签页而不是独立面板2.1 从工具切换到上下文融合的产品逻辑传统 AI 工具的使用路径是人找工具你有一个需求然后主动打开某个应用去满足它。但浏览器场景下用户 90% 的时间都花在标签页里需求是在浏览过程中自然产生的——看到一篇长文想总结、看到一个表单想自动填、看到一段代码想解释。这时候如果还要切出去体验就断了。EO2Weave 的设计逻辑是工具找人扩展常驻在标签页通过侧边栏或悬浮入口随时唤起。这个思路其实和当年的划词翻译、网页批注是一脉相承的只不过现在处理的不再是翻译这种单一任务而是通用的 AI 能力。我实测下来这种常驻设计最大的价值在于保留了页面上下文。独立面板里你问 AI这篇文章讲了什么AI 是不知道的你得手动喂内容。而常驻扩展可以直接读取当前标签页的 DOMAI 天然就知道你在看什么。这个差别看起来小实际体验差距非常大。2.2 技术架构的三个核心层拆开来看这个扩展的架构可以分成三层接入层负责和 AI 服务通信核心是 Codex OAuth 认证流程。这一层解决我是谁、我能调用什么能力的问题。上下文层负责从当前页面提取信息核心是 WebMCP 协议。这一层解决AI 能看到什么的问题。交互层负责 UI 呈现和用户操作包括侧边栏、悬浮按钮、快捷键等。这一层解决用户怎么用的问题。三层里接入层和上下文层是技术难点交互层是体力活。很多同类扩展只做了交互层接入层用硬编码的 API Key上下文层用简单的document.body.innerText抓取结果就是既不安全也不准确。EO2Weave 的价值就在于把前两层做扎实了。2.3 为什么选 Codex OAuth 而不是直接塞 API Key这是整个设计里我最认可的一个决策。早期很多 AI 扩展的做法是让用户在设置页填一个 API Key扩展把它存在chrome.storage.local里每次请求带上。这个方案的问题有三个第一密钥暴露风险。扩展的存储虽然相对隔离但只要有storage权限的代码就能读到一旦扩展被注入恶意脚本密钥就泄露了。第二用户体验差。普通用户根本不知道去哪找 API Key填错一个字符就报错。第三无法做权限分级。一个 Key 就是全权限没法限制这个扩展只能调用总结功能不能调用其他能力。Codex OAuth 走的是标准的授权码流程用户在授权页面登录扩展拿到的是一个短期有效的 access token而不是长期密钥。token 过期了用 refresh token 换新的refresh token 存在服务端或者加密存储里。这样即使 token 泄露影响范围也有限而且可以随时吊销。提示OAuth 流程里最容易踩的坑是 redirect URI 的配置。Chrome 扩展的 redirect URI 格式是https://extension-id.chromiumapp.org/这个必须在授权服务端提前注册否则回调会被拒绝。2.4 WebMCP 解决的是什么问题WebMCP 这个词可能很多人第一次听。简单说它是让网页主动告诉 AI 自己能做什么的一套约定。传统做法是扩展去猜页面结构——找article标签、找main内容区、过滤掉导航和广告。但网页千奇百怪猜的准确率很低。WebMCP 的思路反过来网页开发者通过标准化的方式声明我这里有一个可读的文章主体我这里有一个可填的表单我这里有一个可点击的操作按钮扩展直接读这些声明就行。这就像给网页装了一个AI 可读的说明书扩展不用再靠启发式规则去猜。我实测下来支持 WebMCP 的页面内容提取准确率能从 60% 左右提升到 90% 以上。当然目前支持 WebMCP 的网站还不多所以扩展通常要双轨并行优先读 WebMCP 声明读不到再回退到 DOM 启发式提取。3. 核心细节解析Codex OAuth 与 WebMCP 的落地要点3.1 Codex OAuth 的完整授权流程拆解OAuth 在浏览器扩展里的实现和普通 Web 应用不太一样因为扩展没有传统的页面跳转能力需要用chrome.identityAPI 来处理。完整流程我拆成五步发起授权请求扩展构造一个授权 URL带上client_id、redirect_uri、response_typecode、scope等参数。打开授权页面用chrome.identity.launchWebAuthFlow打开这个 URL它会弹出一个窗口让用户登录授权。捕获回调用户授权后服务端会重定向到redirect_urilaunchWebAuthFlow会捕获这个重定向并返回完整 URL。换取 token从返回的 URL 里解析出code然后用code去 token 端点换取access_token和refresh_token。存储与刷新把 token 存起来请求时带上Authorization: Bearer token过期了用 refresh token 刷新。关键代码大概长这样const authUrl new URL(https://auth.example.com/authorize); authUrl.searchParams.set(client_id, CLIENT_ID); authUrl.searchParams.set(redirect_uri, chrome.identity.getRedirectURL()); authUrl.searchParams.set(response_type, code); authUrl.searchParams.set(scope, ai.summarize ai.qa); chrome.identity.launchWebAuthFlow( { url: authUrl.toString(), interactive: true }, async (redirectUrl) { if (chrome.runtime.lastError || !redirectUrl) { console.error(授权失败, chrome.runtime.lastError); return; } const code new URL(redirectUrl).searchParams.get(code); const tokens await exchangeCodeForTokens(code); await chrome.storage.local.set({ tokens }); } );这里有个细节要注意chrome.identity.getRedirectURL()返回的格式是https://extension-id.chromiumapp.org/这个值必须在授权服务端注册为合法的 redirect URI否则会报redirect_uri_mismatch。3.2 Token 存储的安全策略拿到 token 之后怎么存是个容易被忽视但很重要的点。我的建议是分层存储access_token有效期短通常 1 小时存在chrome.storage.session里浏览器关闭就清空。refresh_token有效期长存在chrome.storage.local里但要做加密处理。加密密钥不要硬编码在代码里可以用chrome.storage.local存一个随机生成的密钥虽然这不是绝对安全但能挡住大部分低级攻击。注意chrome.storage.session默认只有扩展的 service worker 能访问content script 读不到这正好符合token 不暴露给页面的安全原则。3.3 WebMCP 的声明格式与读取方式WebMCP 的核心是网页通过特定的 DOM 属性或 meta 标签声明自己的能力。常见的声明方式有几种用>function extractPageContext() { // 优先读 WebMCP 声明 const mcpArticle document.querySelector([data-mcp-rolearticle]); if (mcpArticle) { return { source: webmcp, content: mcpArticle.innerText }; } // 回退到启发式提取 const fallback document.querySelector(article, main, [rolemain]); return { source: heuristic, content: fallback ? fallback.innerText : document.body.innerText }; }回退逻辑很重要因为目前支持 WebMCP 的站点还是少数。启发式提取的规则我一般按优先级排articlemain[rolemain] 最大文本块。最后那个最大文本块是个兜底策略遍历所有div算文本长度取最长的那个。3.4 内容提取的清洗与截断提取出来的文本不能直接喂给 AI得先清洗。要处理的东西包括去掉导航栏、页脚、广告、评论区这些噪音把连续的空白字符压缩把超长内容截断到模型能接受的范围内。截断策略我推荐首尾保留 中间摘要保留开头 2000 字和结尾 1000 字中间部分如果太长就按段落采样。这样既保留了文章的主旨和结论又不会超出 token 限制。直接从头截断是最差的做法因为很多文章的重点在结尾。4. 实操过程从零搭建一个类似的扩展4.1 项目初始化与 manifest 配置先建目录结构核心文件是manifest.json、background.js、content.js、sidebar.html。manifest 用 V3 版本关键配置如下{ manifest_version: 3, name: EO2Weave, version: 1.0.0, permissions: [identity, storage, activeTab, scripting], host_permissions: [https://auth.example.com/*, https://api.example.com/*], background: { service_worker: background.js }, content_scripts: [{ matches: [all_urls], js: [content.js], run_at: document_idle }], action: { default_popup: popup.html }, side_panel: { default_path: sidebar.html } }权限这块要克制。activeTab比all_urls更安全因为它只在用户主动点击扩展时才授予当前标签页的访问权。scripting用来动态注入脚本identity用来做 OAuthstorage用来存 token。4.2 OAuth 授权模块的实现background service worker 里实现授权逻辑。注意 V3 的 service worker 是事件驱动的不能常驻所以状态要存在 storage 里不能存在全局变量里。async function ensureAuthenticated() { const { tokens } await chrome.storage.local.get(tokens); if (tokens tokens.expiresAt Date.now() 60000) { return tokens.accessToken; } if (tokens tokens.refreshToken) { return await refreshAccessToken(tokens.refreshToken); } return await startAuthFlow(); }这里有个坑service worker 随时可能被浏览器回收所以startAuthFlow里的回调不能依赖内存状态。我踩过一次授权窗口还开着service worker 被回收了回调直接丢失。解决办法是把授权流程的中间状态也存到 storage 里。4.3 页面上下文提取与 WebMCP 集成content script 负责提取页面内容。它跑在页面的上下文里能直接访问 DOM但不能直接访问扩展的 storage需要通过消息传递。提取逻辑我前面讲过这里补充一个细节提取要在document_idle之后执行太早的话 DOM 还没渲染完。chrome.runtime.onMessage.addListener((msg, sender, sendResponse) { if (msg.type EXTRACT_CONTEXT) { const ctx extractPageContext(); sendResponse({ ok: true, data: ctx }); } return true; // 保持消息通道开放 });return true这行很关键不加的话异步sendResponse会失败。这个坑我见过太多人踩。4.4 侧边栏 UI 与消息通信侧边栏用chrome.sidePanelAPI 实现点击扩展图标时打开。UI 和 background 之间通过chrome.runtime.sendMessage通信background 和 content script 之间也通过消息传递。整条链路是侧边栏发请求 → background 处理 → 需要页面内容时向 content script 要 → 拿到内容后调 AI API → 返回结果给侧边栏。消息通信要注意超时处理。content script 如果因为页面卡死没响应background 会一直等。我一般加个 3 秒超时超时就回退到用chrome.scripting.executeScript直接注入提取代码。4.5 参数选择与性能调优几个关键参数我列一下实测值参数推荐值说明内容截断长度8000 字符超过这个长度模型响应明显变慢消息超时3000ms太短容易误判太长影响体验token 刷新提前量60s提前刷新避免请求时刚好过期提取防抖500ms页面动态加载时避免频繁提取内容截断长度这个值不是拍脑袋定的。我测过 4000、8000、16000 三档4000 经常丢关键信息16000 响应时间翻倍8000 是平衡点。当然这取决于你用的模型上下文窗口大的模型可以放宽。5. 常见问题与排查技巧实录5.1 授权相关的典型故障问题一redirect_uri_mismatch。这个几乎每个做 OAuth 的人都遇到过。原因是扩展 ID 变了比如你重新加载了未打包扩展导致getRedirectURL()返回的地址和服务端注册的不一致。解决办法是在manifest.json里固定key字段这样扩展 ID 就稳定了。问题二授权窗口打开后白屏。通常是授权 URL 构造错误或者服务端不支持在弹窗环境里渲染。排查方法是把授权 URL 复制到普通标签页打开看能不能正常显示。问题三token 刷新失败但没报错。这个最坑因为 refresh token 可能被服务端静默吊销了。我的做法是刷新失败时清空本地 token重新走完整授权流程而不是反复重试。5.2 内容提取的准确率问题提取不准是最高频的抱怨。我整理了一个排查表现象可能原因解决方向提取到导航栏文字选择器优先级错误调整启发式规则顺序内容为空页面用 iframe 或 shadow DOM递归遍历 iframe穿透 shadow root内容重复页面有隐藏的重复 DOM过滤display:none的元素提取到乱码编码问题检查页面 charset必要时转码shadow DOM 这个特别容易被忽视。现在很多现代网站用 Web Components内容藏在 shadow root 里普通的querySelector根本查不到。解决办法是递归遍历所有元素的shadowRoot属性。5.3 消息通信的坑前面提过return true的问题这里再补充一个content script 在页面刷新后会失效。如果你在 background 里缓存了 content script 的引用页面一刷新就断了。正确做法是每次通信前用chrome.tabs.sendMessage重新发失败再注入。还有一个是跨域 iframe 的消息隔离。content script 默认只在顶层框架运行iframe 里的内容读不到。需要在 manifest 里配置all_frames: true但这样又会导致同一个页面注入多次得用标志位去重。5.4 我踩过的三个真实坑第一个坑service worker 被回收导致授权状态丢失。前面提过解决办法是把中间状态持久化。这个坑花了我整整一个下午才定位到因为日志里完全看不出问题只是偶尔授权失败。第二个坑WebMCP 声明和实际内容不一致。有些网站声明了>
返回列表