Context7:给 AI 编程助手注入实时文档的 MCP 工具
Context7 是一个给 AI 编程助手补充实时官方文档上下文的工具,核心解决的问题是:大模型的训练数据有截止日期,新框架 API 或频繁变动的库往往使用旧版写法,导致生成的代码无法运行。 工作流程 用户提问 ↓ AI 判断需要文档 ↓ Context7 拉取对应库的最新文档 ↓ 把文档片段注入 Prompt ↓ 模型基于真实文档生成代码例如询问"Next.js 15 middleware 怎么写",普通模型可能给出 Next.js 12 的旧 API,Context7 会先拉取最新 Next.js 文档再生成答案。 与 RAG 的区别维度 RAG Context7数据来源 用户自定义文档库 官方文档仓库更新频率 手动维护 跟随上游自动同步适合场景 内部知识库 公开库文档集成方式 自建 pipeline MCP 协议MCP 接入配置 Context7 提供 MCP server,支持 Cursor、Claude Code、OpenCode 等工具: { "mcpServers": { "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"] } } }配置后在对话中使用 use context7 指令触发文档检索: Next.js 15 的 middleware 怎么配置重定向?use context7支持的文档范围 常见库均有覆盖:React / Next.js / Remix Node.js / Bun LangChain / LangGraph OpenAI SDK / Anthropic SDK Prisma / Drizzle / TypeORM Docker / Kubernetes 各大主流 npm 包在 AI Agent 架构中的位置 OpenCode / Claude Code ├── LLM ├── Browser ├── Terminal ├── Skills ├── Context7 / RAG ← 文档检索层 └── MCP ToolsContext7 填补了"文档检索层"——让 Agent 在生成代码时能查到当前正确的 API,而不是凭训练数据猜测。 替代方案工具 定位Context7 公开库官方文档Mintlify Scraper 自定义文档爬取Crawl4AI 通用网页抓取自建 pgvector RAG 私有文档库对于内部项目文档,更适合自建 RAG(pgvector + embedding);对于开源库,直接用 Context7 省去维护成本。
Playwright 定位网页元素:getByText / getByRole 比 CSS Selector 稳在哪
用 Playwright 自动化网页时,很多人习惯用 CSS selector(page.locator(".btn-submit")),但这类 selector 抗变化能力差——前端一改 class 名就失效。Playwright 提供了一套语义化定位器,稳定性高很多。 语义化定位器 getByText 按可见文本定位,最常用: await page.getByText('登录').click(); await page.getByText('提交订单').click();支持正则: await page.getByText(/确认.*订单/).click();getByRole 按 ARIA role 定位,最语义化: await page.getByRole('button', { name: '提交' }).click(); await page.getByRole('link', { name: '首页' }).click(); await page.getByRole('textbox', { name: '搜索' }).fill('hello');常用 role:button、link、textbox、checkbox、listbox、dialog、heading getByPlaceholder 按 input placeholder 定位: await page.getByPlaceholder('请输入手机号').fill('138xxxx'); await page.getByPlaceholder('密码').fill('password123');getByLabel 按 <label> 文本定位关联的 input: await page.getByLabel('邮箱').fill('test@example.com'); await page.getByLabel('记住我').check();getByTestId 按 data-testid 属性,测试环境最稳定的方案: // HTML: <button data-testid="submit-btn">提交</button> await page.getByTestId('submit-btn').click();为什么比 CSS Selector 稳定位方式 变化敏感度 说明.btn-primary-v2 高 class 名经常随版本变#submit > div > button 高 DOM 结构一变就失效getByText('提交') 低 文本变了才失效getByRole('button', {name: '提交'}) 低 语义稳定getByTestId('submit') 最低 开发者明确标记处理复杂场景 多个相同文本时,用 nth 或组合定位: // 第二个"删除"按钮 await page.getByText('删除').nth(1).click();// 在某个容器内查找 await page.locator('.order-list').getByRole('button', { name: '删除' }).first().click();等待元素可交互: await page.getByRole('button', { name: '提交' }).waitFor({ state: 'visible' }); await page.getByRole('button', { name: '提交' }).click();真正难处理的场景 语义化定位器解决不了的几种情况,才需要 AI Vision Agent:Canvas 元素(无 DOM 结构) Shadow DOM 内的元素 动态随机 class 名(某些 React 框架) 无文字的图标按钮 iframe 跨域内容对于这些场景,可以用 Stagehand 等 AI 驱动的自动化工具: await page.act("点击右上角的关闭按钮");让大模型看截图决定点击位置,不依赖 selector。 实际推荐策略优先用 getByRole + getByText,覆盖 80% 常规场景 次选 getByPlaceholder / getByLabel(表单场景) 开发阶段加 data-testid,测试最稳定 Shadow DOM / Canvas 等场景才用 AI Vision不要一开始就上 AI Vision,定位器够用的情况下更快、更稳、Token 消耗为零。
Chrome CDP 远程调试:--remote-debugging-port 与 Node.js Hook 框架
CDP 远程调试 启动浏览器并开放调试端口 # Chrome chrome.exe --remote-debugging-port=9222# Edge(建议指定独立 user-data-dir) msedge.exe --remote-debugging-port=9222 --user-data-dir=D:\edge_debug获取 WebSocket 调试地址 浏览器启动后,访问: http://127.0.0.1:9222/json返回当前所有 Tab 的信息,其中 webSocketDebuggerUrl 是关键字段: { "id": "ABC123", "title": "test page", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/ABC123" }连接 DevTools 前端 devtools://devtools/bundled/devtools_app.html?ws=127.0.0.1:9222/devtools/page/ABC123整体链路:--remote-debugging-port 开放 CDP 端口 → /json 获取 ws 地址 → DevTools 前端通过 ws 接入。 Node.js Hook 三件套 在 Node 程序里注入以下 hook 可以拦截运行时数据流。 Hook JSON.parse(记录解析数据) const fs = require('fs'); const _parse = JSON.parse;JSON.parse = function (text, reviver) { const result = _parse.call(this, text, reviver); // 只保存含 "raw" key 的对象 if (result && typeof result === 'object' && 'raw' in result) { fs.appendFileSync('./json_log.txt', JSON.stringify({ time: Date.now(), data: result }) + '\n'); } return result; };过滤条件也可以改为 /"raw"\s*:/.test(text) 在原始字符串层面筛选,减少反序列化开销。 Hook TextDecoder.decode(截获二进制转字符串) const { TextDecoder } = require('util'); const _decode = TextDecoder.prototype.decode;TextDecoder.prototype.decode = function (input, options) { const result = _decode.call(this, input, options); console.log('[TextDecoder]', result); return result; };适合抓 WebSocket 二进制帧、CDP 协议数据等场景。注意 Buffer.toString('utf-8') 走的是另一条路径,需要单独 hook Buffer.prototype.toString。 Hook AES 加密(抓密钥与明文) const crypto = require('crypto'); const _createCipheriv = crypto.createCipheriv;crypto.createCipheriv = function (algorithm, key, iv, options) { console.log('[AES key]', { algorithm, key: key?.toString?.('hex'), iv: iv?.toString?.('hex') }); const cipher = _createCipheriv.call(this, algorithm, key, iv, options); const _update = cipher.update; cipher.update = function (data) { console.log('[AES plaintext]', data?.toString?.() ?? data); return _update.apply(this, arguments); }; return cipher; };Hook createDecipheriv 的结构完全对称,可以抓解密后的明文。 注意事项JSON.parse 被局部引用绕过时(const p = JSON.parse; p(...)),改 global.JSON.parse 更彻底 大流量场景建议加过滤条件(text.length < 5000)避免日志刷屏 Node.js 版本 >= 11 才有全局 TextDecoder,旧版用 require('util').TextDecoder
Edge/Chrome CDP 远程调试与 Node.js Hook TextDecoder、JSON、AES
Edge/Chrome CDP 远程调试 1. 启动浏览器开启调试端口 # Edge msedge.exe --remote-debugging-port=9222 --user-data-dir=D:\edge_debug# Chrome chrome.exe --remote-debugging-port=92222. 获取 WebSocket 调试地址 访问: http://127.0.0.1:9222/json返回 JSON,找 webSocketDebuggerUrl: { "title": "test page", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/ABC123" }3. 用 DevTools 前端连接 把 ws 地址传给 DevTools 前端: devtools://devtools/bundled/devtools_app.html?ws=127.0.0.1:9222/devtools/page/ABC123在 Edge/Chrome 地址栏直接输入即可,打开完整的开发者工具界面。 核心结构: 浏览器(Edge/Chrome) ↓ WebSocket (CDP) DevTools Frontend(网页 UI)Node.js TextDecoder 支持情况 Node.js ≥ 11 支持 TextDecoder,Node ≥ 16/18 更稳定: const { TextDecoder, TextEncoder } = require('util');const decoder = new TextDecoder('utf-8'); const buf = Buffer.from([0xe4, 0xbd, 0xa0]); // "你" console.log(decoder.decode(buf)); // 你Hook TextDecoder.decode 用于拦截 WebSocket / CDP 协议里的二进制转文本: const { TextDecoder } = require('util');const _decode = TextDecoder.prototype.decode;TextDecoder.prototype.decode = function (input, options) { const result = _decode.call(this, input, options); console.log('[TextDecoder.decode]', result); return result; };全局 hook(拦截所有实例): const orig = TextDecoder;global.TextDecoder = function (...args) { const instance = new orig(...args); const _decode = instance.decode; instance.decode = function (...params) { const res = _decode.apply(this, params); console.log('[WS decode]', res); return res; }; return instance; };Hook JSON.parse 并落盘 const fs = require('fs'); const _parse = JSON.parse;JSON.parse = function (text, reviver) { try { const result = _parse.call(this, text, reviver); // 只保存含 "raw" 字段的数据 if (result && typeof result === 'object' && 'raw' in result) { fs.appendFileSync( './json_raw_log.txt', JSON.stringify({ time: Date.now(), data: result }) + '\n' ); } return result; } catch (e) { return _parse.call(this, text, reviver); } };按关键字过滤避免刷屏: // 只记录长度 < 5000、包含关键字的 if (typeof text === 'string' && text.length < 5000 && text.includes('method')) { fs.appendFileSync('./log.txt', text + '\n'); }Hook Node.js AES 加密 Hook crypto.createCipheriv 抓加密前的明文、key、IV: const crypto = require('crypto'); const _createCipheriv = crypto.createCipheriv;crypto.createCipheriv = function (algorithm, key, iv, options) { console.log('[AES createCipheriv]', { algorithm, key: Buffer.isBuffer(key) ? key.toString('hex') : key, iv: Buffer.isBuffer(iv) ? iv.toString('hex') : iv }); const cipher = _createCipheriv.call(this, algorithm, key, iv, options); const _update = cipher.update; const _final = cipher.final; cipher.update = function (data, inputEncoding, outputEncoding) { console.log('[AES encrypt input]', data?.toString?.() ?? data); return _update.apply(this, arguments); }; cipher.final = function (outputEncoding) { const res = _final.apply(this, arguments); console.log('[AES encrypt output]', res); return res; }; return cipher; };解密 hook(对称): const _createDecipheriv = crypto.createDecipheriv;crypto.createDecipheriv = function (algorithm, key, iv) { const decipher = _createDecipheriv.call(this, algorithm, key, iv); const _update = decipher.update; decipher.update = function (data) { const res = _update.apply(this, arguments); console.log('[AES decrypt result]', res.toString()); return res; }; return decipher; };组合 hook(实战抓包) WebSocket + 加密接口逆向的标准三件套:TextDecoder.prototype.decode — 抓二进制转文本 JSON.parse — 抓接口返回 JSON crypto.createCipheriv/createDecipheriv — 抓 AES 明文和密钥部分代码直接用 Buffer.toString('utf-8') 而不经过 TextDecoder,还需要额外 hook: const _toString = Buffer.prototype.toString;Buffer.prototype.toString = function (encoding, ...rest) { const result = _toString.call(this, encoding, ...rest); if (encoding === 'utf8' || encoding === 'utf-8') { console.log('[Buffer.toString]', result.slice(0, 200)); } return result; };
SyntaxError: Unexpected identifier '目':文本被贴进 JS 文件
看到这个报错,几乎不用想: 题 目 AI辅助的全方位科研管理与创作平台 ^ SyntaxError: Unexpected identifier '目' at wrapSafe (node:internal/modules/cjs/loader:1637:18)Node 把这一行当代码解析了,而中文字符不是合法 JS 标识符,直接崩。 真正原因 八成是你想读的文件内容被复制粘贴到 JS 源码里了。你的 .js 文件现在长这样: const fs = require('fs') rd = fs.readFileSync("1.txt") console.log(rd.length)题 目 AI辅助的全方位科研管理与创作平台 // ← 这一行是裸文本裸文本既不是字符串(没引号)也不是注释(没 // 或 /* */),Node 只能当代码解析,然后崩在第一个中文字符上。 正确姿势 JS 文件里只留代码,文本内容留在 1.txt 里: // index.js const fs = require("fs");const text = fs.readFileSync("1.txt", "utf8"); console.log(text.length);# 1.txt 题 目 AI辅助的全方位科研管理与创作平台顺手补:readFileSync 不传 encoding 的坑 fs.readFileSync("1.txt") 返回的是 Buffer,不是字符串: const rd = fs.readFileSync("1.txt"); console.log(rd.length); // 字节数,中文会算错想拿字符长度就要传编码: const text = fs.readFileSync("1.txt", "utf8"); console.log(text.length); // 字符数 console.log(Buffer.byteLength(text, "utf8"));// 字节数一个中文字符在 UTF-8 里是 3 字节,字符长度和字节长度会差好几倍——涉及"长度"就要先想清楚要哪种。 触类旁通:类似的 SyntaxError 同样是"文件混了不该混的东西",报错方向不同:报错 通常原因Unexpected identifier '目' 中文文本被贴进 .jsUnexpected token '<' HTML 页面被当 JS 加载(一般是路径错)Unexpected token 'export' ESM 语法进了 CJS 文件(改 .mjs 或加 "type": "module")Unexpected token '?' / ?? / &&= 老 Node 不认新语法,升级 NodeInvalid or unexpected token 指向 文件带了 UTF-8 BOM,重存为无 BOM一句话总结 看到 Unexpected identifier '中文'——先打开源码文件,把混进去的文本挪出去。readFileSync 记得传 "utf8"。
