QVeris的官方IDE插件开发实录:从 OAuth 登录到 MCP 自动接入
· 约 28 分钟阅读 · 阅读 --
Last updated on

QVeris的官方IDE插件开发实录:从 OAuth 登录到 MCP 自动接入

作者: Alex Xiang


这篇文章写的是我自己为 QVeris 做官方 IDE 插件时的一次复盘。最初我想解决的问题其实并不复杂:用户在 Cursor 或 VS Code 里装好插件之后,不应该再去手动折腾 API Key、MCP 配置和规则文件。理想状态是,登录一次,插件就把后面的接线工作都做完,用户直接进 Chat 开始用工具。

真正动手后我很快发现,这不是“做个登录按钮”那么简单。浏览器登录回调、密钥保存、不同 IDE 的配置文件路径、规则文件注入、首次使用引导,以及版本升级之后如何替换旧规则文件,这些地方都很容易把体验做断。这个插件后来真正成型,也恰恰是因为我把这些不显眼的环节一段一段接起来了。

这篇复盘主要基于插件源码、README,以及我开发过程中反复对照的几份 VS Code 扩展文档,尤其是 Webview APIContribution PointsSecretStorage。由于 Cursor 本质上仍然建立在 VS Code 扩展模型之上,所以这几份文档几乎就是整个实现的地基。

如果你想先看成品和源码,可以先记住两个入口:QVeris 官网QVeris 官方 IDE 插件仓库。前者对应产品与服务本身,后者对应本文讨论的扩展实现。

一、先说结论:这不是一个“登录插件”,而是一个“接线插件”

如果只看 README,你会得到一个比较直白的印象:登录 QVeris,自动拿 API Key,自动安装 MCP,自动写规则文件,然后在 AI Chat 里开始用工具。这些描述都没错,但还不够准确。

更准确的说法应该是:它在 IDE 里做了一层“接线工作”。

这层接线工作至少包括四步:

  1. 把用户从 IDE 带到浏览器完成认证,再把结果回传回 IDE。
  2. 把认证结果拆解成真正有用的东西,例如邮箱、访问令牌和 API Key。
  3. 把 API Key 写入对应 IDE 认识的 mcp.json,让 MCP Server 变成可调用状态。
  4. 把规则文件写进工作区,让 AI 知道这个 MCP Server 应该怎么搜工具、怎么执行工具、怎么进一步生成代码。

很多插件做到第二步就收工了。我在做 QVeris 时刻意把链路推进到第四步,因为我希望它不是一个只负责认证的插件,而是一个真正把能力接通的基础设施插件。

QVeris plugin architecture

二、从 package.json 开始,能看出它的基本思路

扩展的入口其实很典型。package.json 里声明了几个关键信息:

  • 它是一个标准 VS Code 扩展,engines.vscode 指向 ^1.85.0
  • 激活时机是 onStartupFinished,也就是说它希望尽早接管初始化逻辑。
  • 它注册了一个独立的 Activity Bar 容器 qverisAi,里面放了一个 webview 类型的 Home 视图。
  • 它还注册了一组命令,例如登录、复制 API Key、刷新登录状态、查看规则文本和尝试示例提示词。

这正好对上了 Contribution Points 文档 里的几类典型能力:viewsContainersviewscommandsconfiguration。换句话说,我没有刻意发明新轮子,而是尽量站在 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 都当成一个壳,而是明确区分了 vscodecursortraekirocodebuddylingmaqoder。这意味着从第一天起,这就不是一个只服务于 Cursor 的小插件,而是一个面向多个宿主、多种分发形态的官方扩展。

第二,它没有走复杂的认证抽象层,而是采用最常见、也最容易调试的模式:openExternal 打开浏览器,登录成功后通过自定义 URI Scheme 回调回来,再由扩展里的 registerUriHandler 接住。

这样的好处是简单、直接、兼容性强;坏处是开发期非常容易被回调 Scheme、编码、状态参数校验这些细节绊倒。所以源码里会留下大量日志,甚至把 URI 的 schemeauthoritypathquery 都逐项打出来。对这类登录回调问题来说,日志不是装饰,而是调试本体。

四、它把“登录成功”拆成了三份数据:邮箱、访问令牌、API Key

这部分是我当时刻意做得比较扎实的一段链路。

从用户视角看,登录成功就是一个动作。但从扩展视角看,这个动作至少要产出三样东西:

  • 访问令牌,用来继续向 QVeris 后端取用户信息和 API Key。
  • 邮箱,用来在 UI 里显示当前账号。
  • API Key,用来真正配置 MCP。

源码里的处理链路大致是这样的:

  1. 回调 URI 里拿到 access_token 和状态参数。
  2. 校验状态参数,避免 CSRF 风险。
  3. 优先尝试从 JWT 里解出邮箱。
  4. 如果 JWT 里没有邮箱,再去请求 /rpc/v1/auth/userinfo
  5. 再去请求 API Key 列表与完整 Key。
  6. 把邮箱、访问令牌、API Key 分别存起来。

这个设计里有两个现实主义细节。

第一,邮箱不是只靠一个固定字段去取,而是做了多层兜底。对接真实后端接口时,这种“字段路径并不完全稳定”的情况并不少见。更理想的状态当然是接口契约更硬,但在客户端里做兜底,能显著降低一线故障率。

第二,获取 API Key 时我先列出现有 Key,再去拿完整 Key,而不是在客户端擅自新建一个。这个选择是有意为之。它减少了意外创建凭证的风险,也让权限边界更清晰。

五、为什么要同时用 SecretStorageglobalState

这也是一个容易被忽略,但很能体现工程判断的点。

按照 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>"
      }
    }
  }
}

为了做到“尽量不破坏用户现有配置”,源码里还做了两件事:

  1. 保留其他已经存在的 MCP Server,只更新 qveris 这一项。
  2. 注销时不直接删掉整段配置,而是把 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 的实际使用路径我尽量压缩成了一条很短的链路:

  1. 安装扩展,打开左侧的 QVeris AI 侧边栏。
  2. 点击 Sign in with Browser,在浏览器里完成登录。
  3. 登录成功后,确认扩展已经为当前 IDE 写好 mcp.json
  4. 如果你在 Cursor 里工作,再确认工作区里已经出现 .cursor/rules/qveris.mdc
  5. 打开 Chat,用自然语言描述你的需求,必要时显式引用 @qveris.mdc

如果还没有安装或想先了解插件定位,也可以先从 QVeris 官网 进入,再查看 GitHub 仓库 里的 README 和命令说明;这样对插件做什么、不做什么会有更完整的预期。

如果你想自己核对配置,下面这张表会更直观一些:

宿主回调 SchemeMCP 配置位置规则文件位置
Cursorcursor://~/.cursor/mcp.json.cursor/rules/qveris.mdc
VS Codevscode://<workspace>/.vscode/mcp.json无自动注入
Traetrae://平台相关的 mcp.json.trae/rules/qveris.md
Kirokiro://~/.kiro/settings/mcp.json~/.kiro/steering/qveris.md
Codebuddycodebuddy://~/.codebuddy/mcp.json~/.codebuddy/rules/qveris.mdc
Lingmalingma://平台相关的 mcp.json.lingma/rules/qveris.md
Qoderqoder://平台相关的 mcp.json.qoder/rules/qveris.md

如果只是想先验证链路通不通,我建议从最简单的提示词开始,例如:

搜索一个可以获取实时加密货币价格的工具,并用它查询 BTC 当前价格。@qveris.mdc

或者:

查找一个可以获取股票行情的工具,先验证能用,再给我生成一段 Python 调用代码。@qveris.mdc

这两类提示词都符合插件写进规则文件的预期路径:先搜工具,再执行工具,再决定要不要生成真实代码。

QVeris plugin sequence

十、这个实现最值得学的地方,是它非常清楚“最后一公里”在哪里

回头看这次实现,我最强烈的感受不是“功能终于做完了”,而是“这类插件真正的麻烦点,终于被我一个个摸清楚了”。

麻烦不在登录页。

麻烦在下面这些位置:

  • 浏览器跳回 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 工具中间层。

相关链接