QVeris的官方IDE插件开发实录:从 OAuth 登录到 MCP 自动接入
这篇文章写的是我自己为 QVeris 做官方 IDE 插件时的一次复盘。最初我想解决的问题其实并不复杂:用户在 Cursor 或 VS Code 里装好插件之后,不应该再去手动折腾 API Key、MCP 配置和规则文件。理想状态是,登录一次,插件就把后面的接线工作都做完,用户直接进 Chat 开始用工具。
真正动手后我很快发现,这不是“做个登录按钮”那么简单。浏览器登录回调、密钥保存、不同 IDE 的配置文件路径、规则文件注入、首次使用引导,以及版本升级之后如何替换旧规则文件,这些地方都很容易把体验做断。这个插件后来真正成型,也恰恰是因为我把这些不显眼的环节一段一段接起来了。
这篇复盘主要基于插件源码、README,以及我开发过程中反复对照的几份 VS Code 扩展文档,尤其是 Webview API、Contribution Points 和 SecretStorage。由于 Cursor 本质上仍然建立在 VS Code 扩展模型之上,所以这几份文档几乎就是整个实现的地基。
如果你想先看成品和源码,可以先记住两个入口:QVeris 官网 和 QVeris 官方 IDE 插件仓库。前者对应产品与服务本身,后者对应本文讨论的扩展实现。
一、先说结论:这不是一个“登录插件”,而是一个“接线插件”
如果只看 README,你会得到一个比较直白的印象:登录 QVeris,自动拿 API Key,自动安装 MCP,自动写规则文件,然后在 AI Chat 里开始用工具。这些描述都没错,但还不够准确。
更准确的说法应该是:它在 IDE 里做了一层“接线工作”。
这层接线工作至少包括四步:
- 把用户从 IDE 带到浏览器完成认证,再把结果回传回 IDE。
- 把认证结果拆解成真正有用的东西,例如邮箱、访问令牌和 API Key。
- 把 API Key 写入对应 IDE 认识的
mcp.json,让 MCP Server 变成可调用状态。 - 把规则文件写进工作区,让 AI 知道这个 MCP Server 应该怎么搜工具、怎么执行工具、怎么进一步生成代码。
很多插件做到第二步就收工了。我在做 QVeris 时刻意把链路推进到第四步,因为我希望它不是一个只负责认证的插件,而是一个真正把能力接通的基础设施插件。
二、从 package.json 开始,能看出它的基本思路
扩展的入口其实很典型。package.json 里声明了几个关键信息:
- 它是一个标准 VS Code 扩展,
engines.vscode指向^1.85.0。 - 激活时机是
onStartupFinished,也就是说它希望尽早接管初始化逻辑。 - 它注册了一个独立的 Activity Bar 容器
qverisAi,里面放了一个webview类型的 Home 视图。 - 它还注册了一组命令,例如登录、复制 API Key、刷新登录状态、查看规则文本和尝试示例提示词。
这正好对上了 Contribution Points 文档 里的几类典型能力:viewsContainers、views、commands 和 configuration。换句话说,我没有刻意发明新轮子,而是尽量站在 VS Code 现成的扩展模型上做事情。
很多编辑器插件项目最大的问题不是功能少,而是过度设计。我当时的取舍比较明确:UI 用 WebviewViewProvider,回调用 registerUriHandler,敏感数据用 context.secrets,其余状态塞进 globalState。这条路线不花哨,但很稳,尤其适合一个需要兼顾多个宿主的官方插件。
三、真正的核心,不在侧边栏,而在 OAuth 回调
从实现上看,侧边栏只是入口。真正决定这类插件能不能用的,是浏览器登录怎么回到 IDE。
源码里的处理方式很直接:
const scheme = getIdeScheme();
const callbackUrl = `${scheme}://QverisAI.qveris-ai/auth-callback`;
const fullUrl = `${loginUrl}?f=${encodeURIComponent(state)}&callback_url=${encodeURIComponent(callbackUrl)}`;
这里有两个我当时特别在意的点。
第一,我没有把所有 IDE 都当成一个壳,而是明确区分了 vscode、cursor、trae、kiro、codebuddy、lingma 和 qoder。这意味着从第一天起,这就不是一个只服务于 Cursor 的小插件,而是一个面向多个宿主、多种分发形态的官方扩展。
第二,它没有走复杂的认证抽象层,而是采用最常见、也最容易调试的模式:openExternal 打开浏览器,登录成功后通过自定义 URI Scheme 回调回来,再由扩展里的 registerUriHandler 接住。
这样的好处是简单、直接、兼容性强;坏处是开发期非常容易被回调 Scheme、编码、状态参数校验这些细节绊倒。所以源码里会留下大量日志,甚至把 URI 的 scheme、authority、path、query 都逐项打出来。对这类登录回调问题来说,日志不是装饰,而是调试本体。
四、它把“登录成功”拆成了三份数据:邮箱、访问令牌、API Key
这部分是我当时刻意做得比较扎实的一段链路。
从用户视角看,登录成功就是一个动作。但从扩展视角看,这个动作至少要产出三样东西:
- 访问令牌,用来继续向 QVeris 后端取用户信息和 API Key。
- 邮箱,用来在 UI 里显示当前账号。
- API Key,用来真正配置 MCP。
源码里的处理链路大致是这样的:
- 回调 URI 里拿到
access_token和状态参数。 - 校验状态参数,避免 CSRF 风险。
- 优先尝试从 JWT 里解出邮箱。
- 如果 JWT 里没有邮箱,再去请求
/rpc/v1/auth/userinfo。 - 再去请求 API Key 列表与完整 Key。
- 把邮箱、访问令牌、API Key 分别存起来。
这个设计里有两个现实主义细节。
第一,邮箱不是只靠一个固定字段去取,而是做了多层兜底。对接真实后端接口时,这种“字段路径并不完全稳定”的情况并不少见。更理想的状态当然是接口契约更硬,但在客户端里做兜底,能显著降低一线故障率。
第二,获取 API Key 时我先列出现有 Key,再去拿完整 Key,而不是在客户端擅自新建一个。这个选择是有意为之。它减少了意外创建凭证的风险,也让权限边界更清晰。
五、为什么要同时用 SecretStorage 和 globalState
这也是一个容易被忽略,但很能体现工程判断的点。
按照 SecretStorage 文档,敏感信息应该放进 context.secrets。QVeris 的实现基本遵守了这个边界:
- API Key 放
secrets - Access Token 放
secrets - Email 也优先放
secrets - 一些非敏感的辅助状态,例如版本号、会话 ID、上次写入的邮箱,则放
globalState
这比把所有东西都丢进 globalState 要靠谱得多。尤其是 API Key,这类信息一旦被明文散落到工作区文件、日志或普通状态存储里,后面几乎都会演变成运维问题。
当然,后面还有可以继续收紧的地方。比如现在并没有充分利用 SecretStorage.onDidChange 这类能力去自动刷新 UI;访问令牌的生命周期管理也更像“先把链路接通”,而不是一个完整的会话系统。不过对插件的第一阶段来说,我更优先保证的是端到端闭环。
六、这类插件最难的一步,其实是写 mcp.json
很多人会把这一步想简单:不就是往配置文件里写一段 JSON 吗?
实际上麻烦恰恰在“不是一个 JSON 文件”。
不同宿主的路径不同,结构也不同。QVeris 的实现里不仅区分了不同 IDE 的配置文件位置,还区分了两类配置根键:
- Cursor、Trae、Kiro、Codebuddy、Lingma、Qoder 用
mcpServers - VS Code 工作区场景用
servers
这背后的原因,README 里其实已经讲得很清楚:同样是 MCP 配置,不同 IDE 的约定并不完全一致。插件的职责,就是把这种差异屏蔽掉。
下面这个示意配置,就是它最终想写进去的核心内容:
{
"mcpServers": {
"qveris": {
"command": "npx",
"args": ["@qverisai/mcp"],
"env": {
"QVERIS_API_KEY": "<your-api-key>"
}
}
}
}
为了做到“尽量不破坏用户现有配置”,源码里还做了两件事:
- 保留其他已经存在的 MCP Server,只更新
qveris这一项。 - 注销时不直接删掉整段配置,而是把 API Key 改成占位值,保留结构。
这种做法背后其实是一个很朴素的产品判断:对一个已经手写过 MCP 配置的用户来说,最讨厌的不是功能不够,而是插件自作主张改坏了别的东西。
七、真正把它和一般“登录插件”区分开的,是规则文件注入
如果说写 mcp.json 是把服务“接通”,那么写规则文件就是把服务“讲明白”。
这是整个项目里我最看重的一部分。它不满足于“QVeris MCP 已经安装完成”,而是继续往前做了一步:把关于“怎么搜工具、怎么执行工具、什么时候可以进一步生成代码”的说明,写进工作区规则文件。
在 Cursor 下,这一步会落到 .cursor/rules/qveris.mdc。在其他宿主里,则写到各自对应的位置。更重要的是,它还考虑了版本升级场景:扩展每次激活会比较当前版本号与 globalState 里的旧版本号,必要时强制替换规则文件,保证 prompt 不会长期漂移。
这背后其实是一个很清晰的判断:对于 MCP 来说,安装本身不等于可用。真正的可用,是模型知道什么时候该搜工具,什么时候该执行工具,什么时候该把拿到的 tool_id 进一步写进真实业务代码里。
从这个角度看,我做的这个插件不是单纯的“配置生成器”,而更像一个“行为脚手架”。
这背后还有一个很实际的原因:我开发这个扩展时,本身就长期处在 Cursor + MCP 的工作流里。所以我很清楚,真正缺的从来不是再多一个按钮,而是把模型拉到正确的调用路径上。也正因为如此,我会把“规则注入”放到和“配置写入”几乎同样重要的位置。
八、Home 视图的实现没有很花哨,但很务实
QVeris 的 Home 视图使用了标准的 Webview API。从代码风格上看,它确实有一点“把 HTML、CSS、JS 全塞进一个 TypeScript 字符串里”的味道,但这是我当时为了加快迭代速度做的现实取舍,先把路径跑通,再考虑进一步组件化。
它做了几件很务实的事情:
- 登录前显示欢迎卡片和浏览器登录按钮。
- 登录后显示邮箱和掩码后的 API Key。
- 支持复制 API Key。
- 提供“Try Example Prompt”,尽量把用户送进第一次成功体验。
- 使用
postMessage在 webview 和 extension host 之间同步登录状态。 - 设置 nonce 和 CSP,至少在安全姿势上没有偷懒。
这类 UI 的价值,不在于它长得多华丽,而在于能不能把“我已经装好了插件”带到“我真的调起来一个工具了”。我给 Home 视图定义的任务始终是这个,而不是单纯做一个展示面板。
九、如果你是普通用户,正确的使用路径其实很简单
很多插件明明功能不复杂,却在第一次使用时让人发懵。QVeris 的实际使用路径我尽量压缩成了一条很短的链路:
- 安装扩展,打开左侧的
QVeris AI侧边栏。 - 点击
Sign in with Browser,在浏览器里完成登录。 - 登录成功后,确认扩展已经为当前 IDE 写好
mcp.json。 - 如果你在 Cursor 里工作,再确认工作区里已经出现
.cursor/rules/qveris.mdc。 - 打开 Chat,用自然语言描述你的需求,必要时显式引用
@qveris.mdc。
如果还没有安装或想先了解插件定位,也可以先从 QVeris 官网 进入,再查看 GitHub 仓库 里的 README 和命令说明;这样对插件做什么、不做什么会有更完整的预期。
如果你想自己核对配置,下面这张表会更直观一些:
| 宿主 | 回调 Scheme | MCP 配置位置 | 规则文件位置 |
|---|---|---|---|
| Cursor | cursor:// | ~/.cursor/mcp.json | .cursor/rules/qveris.mdc |
| VS Code | vscode:// | <workspace>/.vscode/mcp.json | 无自动注入 |
| Trae | trae:// | 平台相关的 mcp.json | .trae/rules/qveris.md |
| Kiro | kiro:// | ~/.kiro/settings/mcp.json | ~/.kiro/steering/qveris.md |
| Codebuddy | codebuddy:// | ~/.codebuddy/mcp.json | ~/.codebuddy/rules/qveris.mdc |
| Lingma | lingma:// | 平台相关的 mcp.json | .lingma/rules/qveris.md |
| Qoder | qoder:// | 平台相关的 mcp.json | .qoder/rules/qveris.md |
如果只是想先验证链路通不通,我建议从最简单的提示词开始,例如:
搜索一个可以获取实时加密货币价格的工具,并用它查询 BTC 当前价格。@qveris.mdc
或者:
查找一个可以获取股票行情的工具,先验证能用,再给我生成一段 Python 调用代码。@qveris.mdc
这两类提示词都符合插件写进规则文件的预期路径:先搜工具,再执行工具,再决定要不要生成真实代码。
十、这个实现最值得学的地方,是它非常清楚“最后一公里”在哪里
回头看这次实现,我最强烈的感受不是“功能终于做完了”,而是“这类插件真正的麻烦点,终于被我一个个摸清楚了”。
麻烦不在登录页。
麻烦在下面这些位置:
- 浏览器跳回 IDE 时,Scheme 对不对。
- 同一个插件跑在不同 IDE 里,配置文件到底写到哪。
- 登录成功以后,AI 还是不会用工具怎么办。
- 规则文件更新了,老用户工作区里的旧规则要不要替换。
- 用户点击“Try Example”时,能不能尽快获得一次正反馈。
这些问题每一个都不算大,但如果没有被系统性地解决,最后就会变成“这个插件理论上可用,实际上总差一口气”。这也是我后来越来越确定的一点:官方插件真正该做的,就是把这些最后一公里的坑尽可能填平。
十一、也确实还有不少可以继续打磨的地方
作为作者,复盘里最有价值的部分不是夸自己做了什么,而是明确下一步还该继续补什么。结合当前代码,我觉得至少还有下面几件事值得继续做。
1. 先把安全边界再收紧一点
首先还是安全边界。像本地调试配置、示例配置、打包产物这些地方,都应该继续收紧,确保真实 API Key 不会因为开发便利而误入仓库或产物。
更稳妥的做法应该是:
- 示例配置永远使用占位符。
- 本地调试配置默认加入忽略列表。
- 在打包或发布前加一轮 secret scan。
这不是洁癖,而是官方工具必须具备的基本卫生。
2. extension.ts 现在太胖了
目前的 activate() 已经承担了太多职责:环境检查、日志初始化、URI Handler 注册、命令注册、版本检测、规则文件升级、周期性检查,几乎都挤在一个入口文件里。
短期看这样开发很快,长期看会拖慢维护效率。下一步更合理的方式,是把下面几类逻辑拆出去:
- OAuth 与会话管理
- MCP 配置写入器
- 规则文件同步器
- 首次使用引导
- 版本升级策略
这样一来,后面无论是补测试,还是继续支持更多宿主,都会轻松很多。
3. 配置写入应该更“保守”一点
当前实现已经尽量保留了其他 MCP 配置,但从工程角度看,仍然有几个潜在边角问题:
- 只看第一个 workspace folder,多根工作区场景下不够完整。
- 主要处理 JSON,不处理 JSONC 或注释配置。
- 路径判断靠字符串包含关系,虽然实用,但不够优雅。
这些问题现在不一定会立刻出错,但一旦用户环境稍微复杂一点,就容易冒出来。
4. 规则文件应该逐步从“大段 prompt”进化成“结构化规则”
当前规则文件的价值很明确,但它仍然偏向“大段 prompt 说明”。随着工具生态变复杂,我更希望后面把规则文件拆成几类更稳定的结构:
- 搜工具的原则
- 执行工具的原则
- 生成业务代码时的 API 约束
- 常见错误与回退策略
这样不仅更容易维护,也更适合不同 IDE 之间做差异化适配。
5. 需要补自动化测试,尤其是回调和配置写入
对于这类插件来说,最值得优先补的不是 UI 截图测试,而是以下三类单元测试或集成测试:
- URI 回调参数解析与状态校验
- 不同 IDE 路径下的
mcp.json写入 - 规则文件版本升级与替换逻辑
这些地方一旦回归,用户遇到的问题往往都不是“功能不好用”,而是“完全不可用”。
十二、写在最后:它真正交付的是“可调用性”
如果只把这个项目理解成“给 Cursor 做一个登录入口”,那就低估它了。
QVeris 这个插件真正交付的,不是一个侧边栏,也不是一个按钮,而是一种可调用性:用户装上扩展、登录一次、打开聊天窗口,模型就能在当前工作区里理解 QVeris 的工具入口,并沿着既定路径去搜工具、执行工具、再生成代码。
这也是为什么我想专门写一篇文章把它讲清楚。因为在今天的 AI 开发工具里,最稀缺的从来不是“再多一个模型入口”,而是把模型能力真正接到工程流里。QVeris 的这个官方插件,现阶段做成的,正是这条接线工作。
如果下一步还要继续进化,我最期待的是两件事:一是把会话、配置和规则同步做得更稳;二是把现在这套“以 prompt 为中心的行为脚手架”,逐渐升级成更结构化、更可验证的工具使用协议。那时候,它就不只是一个好用的插件,而会更像一块成熟的 AI 工具中间层。
相关链接
- QVeris 官网:https://qveris.ai
- QVeris 官方 IDE 插件仓库:https://github.com/QVerisAI/vscode-qveris-ai