Bundle
dsh-qixin-insight-mcp-oauth
DeepSeek Harness plugin: one-click OAuth (PKCE) connect to the Qixin Insight MCP server, mounted as a single plugin entry
- Source
- qixin-ai-data
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 5 days ago
Readme
# dsh-qixin-insight-mcp-oauth
[](./LICENSE)
[](https://www.npmjs.com/package/dsh-qixin-insight-mcp-oauth)
[](#环境要求)

> [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:
> 用 OAuth 2.1 + PKCE 授权接入[启信慧眼](https://www.qixin.com) MCP 服务端,
> 把它的工具挂进对话。
装上,在浏览器里登录一次,启信慧眼的企业数据工具就以 `mcp__qixin_insight__<tool>`
出现在对话里。不用贴 API key,不用填服务地址,任何配置文件里都不会出现 token。
- [环境要求](#环境要求)
- [安装](#安装)
- [使用](#使用)
- [工作原理](#工作原理)
- [配置](#配置)
- [安全说明](#安全说明)
- [卸载](#卸载)
- [已知限制](#已知限制)
- [开发](#开发)
- [相关标准](#相关标准)
## 环境要求
| | |
| --- | --- |
| DeepSeek Harness | `dsh` CLI 与一个 profile(下面的例子用 `web`) |
| Node.js | `^22.19.0 \|\| >=24.0.0` |
| 启信慧眼AI开放平台账号 | 能完成授权页登录即可 |
`@deepseek-ai/*` 与 `zod` 由宿主 profile 提供,不需要单独安装:dsh 启动时会把自身依赖
链接进 profile 的模块解析路径。
## 安装
### 让 Agent 装(最省事)
把这句话发给你的 DeepSeek Harness 对话:
```
帮我安装这个插件 https://github.com/qixin-ai-data/dsh-qixin-insight-mcp-oauth
```
### 从 npm 安装
```bash
dsh plugin --profile web add dsh-qixin-insight-mcp-oauth
# 然后重启 dsh
```
### 从源码安装
```bash
git clone https://github.com/qixin-ai-data/dsh-qixin-insight-mcp-oauth.git
cd dsh-qixin-insight-mcp-oauth
npm install && npm run build
dsh plugin --profile web add "link:$(pwd)"
# 然后重启 dsh
```
包内自带 `cordis.patch.yml`(bundle patch),`dsh plugin add` 会完成依赖安装与
bundle 注册,并往 profile 的配置树里合入**一行**插件条目。这一行不带 `config:` 块,
没有任何东西需要填。
启动前可以先验证这一层:
```bash
dsh --profile web --dump-config # 应出现 "# == dsh-qixin-insight-mcp-oauth" 层
```
## 使用
通常什么都不用做。`autoConnect` 默认开启:插件激活且没有有效授权时会自己发起授权、
打开浏览器。登录完成、回调落地,工具就出现了。
如果你关掉了浏览器,或者想重新连接,说一句就行:
| 你说 | 效果 |
| --- | --- |
| 「连接启信慧眼mcp」 | `qixin_insight_mcp_connect`——打开浏览器登录,完成后挂载 MCP 工具 |
| 「启信慧眼mcp连上了吗」 | `qixin_insight_mcp_status`——连接状态、受众绑定、token 过期时间、已挂载的工具前缀 |
| 「断开启信慧眼mcp」 | `qixin_insight_mcp_disconnect`——撤销 refresh_token、卸载工具、删除本地凭证 |
背后是三个工具:
| 工具 | 说明 |
| --- | --- |
| `qixin_insight_mcp_connect` | 发现 → 注册 → 打开浏览器 → 交换 code → 挂载 MCP。已有授权时复用、必要时自动刷新,不会重复弹授权页。 |
| `qixin_insight_mcp_status` | 是否已授权、token 过期时间、已挂载的工具前缀、是否需要重新授权。 |
| `qixin_insight_mcp_disconnect` | 先走 RFC 7009 吊销,再卸载工具并删除本地 grant。 |
token 会在过期前自动刷新,重启宿主后自动恢复连接。这两件事都不需要你操心。
## 工作原理
插件激活时先走 `restore()`:已存 grant 仍在、且受众绑定与配置的 `url` 匹配,就直接
挂载,下面这些一步都不发生。没有可用授权时才走完整的 `authorize()`:
```mermaid
sequenceDiagram
autonumber
participant P as 插件
participant M as 启信慧眼 MCP
participant A as 授权服务器
participant B as 浏览器
P->>M: RFC 9728 取受保护资源元数据
M-->>P: 规范资源标识 + 授权服务器列表
P->>A: RFC 8414 取 AS 元数据
A-->>P: 端点集合(校验 issuer 与所询问的对象一致)
P->>P: RFC 8252 绑环回监听器(随机端口)<br/>此刻才知道确切的 redirect_uri
P->>A: RFC 7591 动态客户端注册(结果缓存复用)
A-->>P: client_id
P->>P: RFC 7636 生成 S256 code_challenge 与 state
P->>B: 打开系统浏览器跳转授权页
B->>A: 用户登录并同意
A->>B: 302 重定向到 redirect_uri
B->>P: 环回端口收到 code,比对 state
P->>B: 授权成功页,2 秒后自关闭
P->>A: RFC 6749 code + code_verifier 交换 token
A-->>P: access_token / refresh_token
P->>P: RFC 8707 存下绑定到规范资源的 grant
P->>P: 以子 fiber 挂载 MCP client,工具可用
```
「打开系统浏览器跳转授权页」这一步,用户看到的就是这一屏:

绑环回监听器必须排在动态注册之前:RFC 7591 要求申报确切的 `redirect_uri`,
而随机端口在绑定前是未知的。
`access_token` 过期前,会话会刷新 token 并重挂 MCP client。重挂是被迫的,
不是偷懒:mcp-client 的 transport 在构造时就把请求头固化成静态的 `requestInit`,
没有换 token 的口子。代价是一次 `tools/list` 往返的工具抖动,且只发生在
token 生存期边界上。
## 配置
**正常使用不需要看这一节。** 每个字段都有默认值,且默认值就是启信慧眼生产环境的正确取值。
这些是留给自建部署、无头环境、多账号场景的应急出口。
要覆盖的话,写在 profile 自己的 `cordis.patch.yml` 里:
```yaml
- id: qixin-insight-mcp
name: 'dsh-qixin-insight-mcp-oauth'
config:
url: https://mcp.your-deployment.example/mcp
openBrowser: false
```
| 字段 | 默认值 | 什么时候才需要改 |
| --- | --- | --- |
| `url` | `https://mcp.qixin.com/mcp` | 指向自建或预发的 MCP 端点 |
| `serverName` | `qixin_insight` | 最好别动:改了会重命名所有 `mcp__qixin_insight__*` 工具,引用它们的提示词或技能会失效 |
| `scope` | `mcp:tools` | 部署方定义了不同的 scope |
| `clientName` | `WorkBuddy` | 只有在启信慧眼同步改白名单时才改,见下方说明 |
| `issuer` | 自动发现 | 部署方不提供 RFC 9728 资源元数据 |
| `callbackHost` | `localhost` | 仅当白名单登记的 `redirect_uri` 主机变化时;只接受 loopback 取值(`localhost`、`127.0.0.1`、`::1`) |
| `callbackPath` | `/oauth/callback` | 仅当白名单登记的 `redirect_uri` 路径变化时 |
| `callbackPort` | `0`(随机) | 授权服务器做完整 URI 精确匹配,而不是 RFC 8252 §7.3 的 host + path 匹配 |
| `authorizeTimeoutMs` | `300000` | 用户需要超过 5 分钟才能登录完 |
| `requestTimeoutMs` | `15000` | 到 OAuth 端点的网络较慢 |
| `refreshSkewMs` | `300000` | 最好别动:超过 token 生存期一半时会被自动压到一半 |
| `openBrowser` | `true` | 无头环境:关掉后授权 URL 会打进日志,手动打开 |
| `autoConnect` | `true` | 你更希望显式调用 `qixin_insight_mcp_connect` |
| `persistCredentials` | `true` | 关掉即仅内存,每次重启都要重新授权 |
| `toolTimeoutMs` | `60000` | MCP 查询耗时较长 |
| `account` | `default` | 一次安装里持有多个启信慧眼身份。小写字母、数字、连字符 |
`clientName`、`callbackHost`、`callbackPath` 不是普通的展示默认值。启信慧眼按注册时提交的
`client_name` 与 `redirect_uris` 做白名单校验,取值不在范围内会在注册阶段返回
HTTP 400,整个流程走不下去。改它们等于要求对方同步改白名单。
## 安全说明
- **公开客户端,无 client_secret。** PKCE 只接受 S256。服务端声明了 PKCE 但不含 S256 时
直接拒绝,而不是悄悄降级成 `plain`。
- **issuer 是校验出来的,不是信任来的。** RFC 8414 §3.3 要求元数据文档里的 `issuer`
与所询问的对象一致。少了这道检查,能在我们被指向的 URL 上提供元数据的攻击者
就可以把整个流程重定向到他自己的服务器。
- **token 不会进 `cordis.yml`。** MCP client 通过 `ctx.plugin()` 以子 fiber 挂载,
而不是写入配置树,所以 `Authorization: Bearer …` 这个头永远不会被序列化到磁盘。
- **grant 存在 `ctx.credentials`**(DSH 凭证服务)而不是自建存储,因为那个 seam
才提供跨进程写互斥。只有非密文的客户端注册信息留在插件自己的存储域里。
- **恢复时强制校验受众绑定。** 已存 grant 的 `resource` 与配置的 `url` 不符时直接丢弃:
它属于另一个身份,而且按 RFC 8707 本来也会被拒。
- **环回监听器只绑 loopback。** `callbackHost` 为 `localhost` 时,同一端口上同时绑
`127.0.0.1` 与 `::1`——`localhost` 解析到哪一栈由系统决定,Node 17+ 和 macOS 常把
`::1` 排在前面。绝不绑 `0.0.0.0` 或 `::`:授权码明文出现在回调请求的 query 里,
绑通配地址等于把它交给同网段的任何人。
- **断开是真的吊销。** `qixin_insight_mcp_disconnect` 会先调用 RFC 7009 吊销端点,再删本地的东西,
所以服务端那侧的 refresh_token 也随之失效。
## 卸载
移除插件**之前**先执行 `qixin_insight_mcp_disconnect`:
```
断开启信慧眼mcp
```
```bash
dsh plugin --profile web remove dsh-qixin-insight-mcp-oauth
# 然后重启 dsh
```
**重启这一步不能省。** `dsh plugin remove` 只改磁盘上的 profile 与 bundle 列表,
正在运行的进程保留本次启动时的 bundle 集合,插件的 fiber 和它注册的工具都还活着——
`qixin_insight_mcp_*` 仍然出现在对话里、仍然能调用,看起来像没卸掉。移除、添加、
更新 bundle 都是这样,只有 profile 或 home 的 `cordis.patch.yml` 编辑才走热重载。
卸载本身不会清理凭证。harness 没有卸载钩子——`disabled: true`、删除配置条目、
热重载、进程退出,cordis 跑的是同一个 disposer,没有任何信号能区分它们。
在那里删 grant 等于为了罕见情形牺牲三种常见情形,也把「重启后自动恢复」这件事废掉了,
所以插件是刻意保留的。跳过断开会留下:
- DSH 凭证服务里的 grant 记录(本地提供方下,即 `$DSH_HOME/.credentials.yaml` 中
`qixin-insight-mcp` 下的一条);
- `qixin_insight_mcp` 存储域里的非密文客户端注册信息;
- **服务端仍然有效的 refresh_token。** 手改凭证文件只是让本机忘掉这个 token,
并不吊销它。只有 `qixin_insight_mcp_disconnect` 会吊销。
插件已经卸掉的情况下,去启信慧眼账号的已授权应用页面撤销授权,再手工删掉残留记录。
## 已知限制
- **一个插件条目对应一个 MCP 端点。** 端点固定是刻意的设计;需要两个的话,
再加一条指向不同 `url`、用不同 `account` 的条目。
- **token 轮换会有短暂的工具抖动。** 重挂时一次 `tools/list` 往返,
只发生在 token 生存期边界上,见[工作原理](#工作原理)。
- **服务端不签发 `refresh_token` 时要重新完整授权。** access_token 过期后会重新走一遍
浏览器流程。
- **没有设置页 UI。** 三个对话工具是唯一入口。第三方插件**是**可以注册 DSH 设置卡片的,
这里不做只是取舍:多一个 UI 面就多一份要同步的状态。以后要加,工具层不用改。
- **卸载后的清理是手动的。** 见[卸载](#卸载)。
## 开发
```bash
npm run build # tsc -b,产出到 lib/
npx tsc -p tsconfig.test.json # 只类型检查测试,不产出
npm test # vitest run
npm run test:coverage # 阈值:lines/functions/statements 85%,branches 80%
```
## 相关标准
RFC [6749](https://www.rfc-editor.org/rfc/rfc6749)(授权码)·
[7009](https://www.rfc-editor.org/rfc/rfc7009)(吊销)·
[7591](https://www.rfc-editor.org/rfc/rfc7591)(动态注册)·
[7636](https://www.rfc-editor.org/rfc/rfc7636)(PKCE)·
[8252](https://www.rfc-editor.org/rfc/rfc8252)(原生应用 / 环回重定向)·
[8414](https://www.rfc-editor.org/rfc/rfc8414)(AS 元数据)·
[8707](https://www.rfc-editor.org/rfc/rfc8707)(资源指示器)·
[9728](https://www.rfc-editor.org/rfc/rfc9728)(受保护资源元数据)
## 许可
[MIT](./LICENSE)
Install
dsh plugin --profile web add github:qixin-ai-data/dsh-qixin-insight-mcp-oauth
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-qixin-insight-mcp-oauth from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.