dsh-mcp-connector 0.2.21 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -1
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/docs/STDIO-SUPPORT.md +2 -0
- package/docs/USER-GUIDE.md +6 -3
- package/lib/connectors/oauth-connector.js +10 -2
- package/lib/constants.js +5 -0
- package/lib/grant-lifecycle.js +47 -0
- package/lib/index.js +739 -116
- package/lib/mcp-provision.js +5 -5
- package/lib/mcp-validation.js +25 -3
- package/package.json +2 -2
- package/ui/index.html +33 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.23] - 2026-08-26
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- OAuth Grant 现在保存 Access Token 的真实过期时间,并在提前刷新遇到网络、发现端点或服务端暂时故障时采用有上限的指数退避自动恢复;只有 `invalid_grant`、`invalid_client` 等明确不可恢复错误才显示“需重新授权”。
|
|
12
|
+
- 启动恢复不再吞掉 Token 刷新异常;脱敏日志会记录阶段、OAuth 错误码、HTTP 状态与永久/暂时分类,且不会输出 Access Token、Refresh Token 或客户端密钥。
|
|
13
|
+
- `grantSharing: "issuer"` 正式生效:同账号、同 issuer/scope 的卡片共享动态客户端与 Grant,后续连接同组卡片不再重复打开授权页;重新授权会把同组已连接卡片迁移到新 Grant。
|
|
14
|
+
- 启动和连接前检测仍启用的 `qcc-mcp-oauth` / `qcc-legal-mcp-oauth` 及其残留 mcp-client 条目;发现同名 Server 时阻断新连接,避免旧 Token 覆盖新连接器凭据。
|
|
15
|
+
|
|
16
|
+
### Verification
|
|
17
|
+
|
|
18
|
+
- 新增 OAuth 刷新失败分类/脱敏/退避、暂时故障自动恢复、永久失效重新授权、同 issuer 顺序与并发共享授权、旧插件冲突阻断回归测试。
|
|
19
|
+
|
|
20
|
+
## [0.2.22] - 2026-08-26
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- 首次连接改为等待 `dsh-mcp-client` 完成 MCP `initialize` 与首次 `tools/list`;启动失败或超时不再提前保存、增加已安装数量或显示“已连接”。
|
|
25
|
+
- stdio 健康状态与工具详情改用 Host 实际注册的 `mcp__<serverName>__*` 工具;未注册工具保持“已配置”并显示可重试诊断。
|
|
26
|
+
- 手动 HTTP、免鉴权市场连接与 JSON 导入统一在落库前执行连接校验;DNS、网络、鉴权或协议失败时保留原配置且不写入无效记录。
|
|
27
|
+
- 市场 Web 请求增加连接、健康检查与工具加载超时,避免详情页无限停留在“加载中”。
|
|
28
|
+
|
|
29
|
+
### Verification
|
|
30
|
+
|
|
31
|
+
- 新增无效 HTTP 不落库、stdio Host 启动失败回滚、Host 工具注册状态和前端有限超时回归测试。
|
|
32
|
+
|
|
7
33
|
## [0.2.21] - 2026-08-25
|
|
8
34
|
|
|
9
35
|
### Documentation
|
|
@@ -359,7 +385,10 @@
|
|
|
359
385
|
- 外部 URL 与导入 Header 执行安全校验。
|
|
360
386
|
- iframe 消息校验同源和消息来源。
|
|
361
387
|
|
|
362
|
-
[Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.
|
|
388
|
+
[Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.23...HEAD
|
|
389
|
+
[0.2.23]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.22...v0.2.23
|
|
390
|
+
[0.2.22]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.21...v0.2.22
|
|
391
|
+
[0.2.21]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.20...v0.2.21
|
|
363
392
|
[0.2.20]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.19...v0.2.20
|
|
364
393
|
[0.2.19]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.18...v0.2.19
|
|
365
394
|
[0.2.18]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.17...v0.2.18
|
package/README.en.md
CHANGED
|
@@ -25,10 +25,10 @@ Browse and install MCP connectors from different providers in DeepSeek Harness D
|
|
|
25
25
|
- Credential and MCP initialize validation before HTTP API-key connectors are saved as installed, plus declarative multi-field credential-to-env bindings for marketplace stdio connectors.
|
|
26
26
|
- Dynamic tool discovery grouped by MCP server, including descriptions, search, batched rendering, and an independent scroll region.
|
|
27
27
|
- Curated prompt templates that can open a DSH conversation and prefill its draft; missing variables are requested before the prompt is sent.
|
|
28
|
-
- Persistent connection lifecycle management: restore on restart, enable/disable, disconnect,
|
|
28
|
+
- Persistent connection lifecycle management: restore on restart, enable/disable, disconnect, retry transient OAuth refresh failures with bounded backoff, and revoke authorization. Cards declaring issuer-level sharing reuse one grant; DCR client secrets remain local to that grant.
|
|
29
29
|
- Built-in, remote, and local catalogs with `published` and `featured` controls.
|
|
30
30
|
- A standalone remote Registry, allowing new marketplace cards to appear after refresh without publishing a new npm version.
|
|
31
|
-
- Explicit, non-destructive migration
|
|
31
|
+
- Explicit, non-destructive migration from the two earlier Qichacha OAuth plugins, plus active-plugin conflict detection that blocks duplicate server management and credential overwrites.
|
|
32
32
|
|
|
33
33
|
<!-- catalog-stats:start -->
|
|
34
34
|
As of 2026-08-25, the public Registry publishes 78 connector descriptors. After merging and deduplicating them with the 4 bundled Qichacha cards, the Marketplace exposes 82 cards across 9 business categories. Recommendations remain limited to the four Qichacha cards, PKULaw, and Wind, for 6 featured cards in total. The Registry evolves independently; the badge shown after a client refresh and the live badges above are the authoritative current counts.
|
|
@@ -111,7 +111,7 @@ npm run dev:ui
|
|
|
111
111
|
|
|
112
112
|
Every Registry merge regenerates `catalog-stats.json`; an hourly workflow in this repository synchronizes the Chinese and English product copy plus a local stats snapshot. The static npm README updates with package releases, while the live badges above read the Registry directly and therefore stay current without another npm release.
|
|
113
113
|
|
|
114
|
-
The current public version is [`dsh-mcp-connector@0.2.
|
|
114
|
+
The current public version is [`dsh-mcp-connector@0.2.22`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.22](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.22).
|
|
115
115
|
|
|
116
116
|
See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
|
|
117
117
|
|
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
- Prompt 模板:使用 `{{company}}` 等变量,发送前填写真实查询主体。
|
|
26
26
|
- 三种接入:OAuth 2.0 PKCE、自定义 HTTP/stdio、导入 `mcpServers` JSON;也支持从连接器描述 URL 安装。OAuth 动态注册兼容公共客户端以及 `client_secret_post` / `client_secret_basic` 机密客户端。
|
|
27
27
|
- 市场 Bearer/API Key 连接器先执行 MCP initialize 连通性与凭据校验,全部 HTTP Server 通过后才持久化凭据并进入“已安装”;stdio 卡片可声明多个本机凭据字段及其环境变量映射。
|
|
28
|
-
- 生命周期管理:连接持久化、重启恢复、启停、断开、OAuth
|
|
28
|
+
- 生命周期管理:连接持久化、重启恢复、启停、断开、OAuth 自动刷新/退避恢复与撤销;同 issuer 卡片可共享一次授权,DCR 返回的客户端密钥与 Token 一同只保存在本机。
|
|
29
29
|
- 目录运营:内置目录、远程 registry、本地覆盖,支持 `published` 上下架与 `featured` 精选。
|
|
30
30
|
- 独立远程 Registry:新市场卡片合并后客户端刷新即可见,无需重新发布 npm;远程不可用时自动回退内置目录。
|
|
31
31
|
- Registry 工具链:Schema/唯一性/密钥审计、MCP/OAuth 无凭据探针、每周健康巡检。
|
|
32
|
-
- 平滑迁移:显式扫描并复制两个旧企查查 OAuth
|
|
32
|
+
- 平滑迁移:显式扫描并复制两个旧企查查 OAuth 插件授权;检测到旧插件仍启用并管理同名 Server 时阻断重复连接,避免凭据相互覆盖。
|
|
33
33
|
- 对话工具:`mcp_connector_catalog`、`connect`、`configure`、`import_json`、`install_from_url`、`status`、`health_check`、`set_enabled`、`disconnect`、`refresh_catalog`、`publish`、`tools_list`。
|
|
34
34
|
|
|
35
35
|
<!-- catalog-stats:start -->
|
|
@@ -106,7 +106,7 @@ npm run dev:ui
|
|
|
106
106
|
|
|
107
107
|
公共 Registry 每次合并后会生成 `catalog-stats.json`;本仓库的定时工作流每小时同步中英文介绍和统计快照。npm 页面中的静态正文随版本发布更新,上方动态统计徽标则直接读取 Registry,可在不发布新 npm 版本时保持实时数量一致。
|
|
108
108
|
|
|
109
|
-
当前公开版本为 [`dsh-mcp-connector@0.2.
|
|
109
|
+
当前公开版本为 [`dsh-mcp-connector@0.2.22`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.22](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.22)。
|
|
110
110
|
|
|
111
111
|
版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
|
|
112
112
|
Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
|
package/docs/STDIO-SUPPORT.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
> 结论:**低成本补齐**——`@deepseek-ai/dsh-mcp-client` 已原生支持 stdio,我们只需在 schema + provisioning 两层「开闸透传」。
|
|
5
5
|
|
|
6
6
|
> **实施状态(2026-08-23)**:本文方案已经落地。schema、手工配置、JSON 导入、provisioning、Registry Schema、目录安全审计、健康状态、详情提示和自动测试均已完成;下文保留实施前的代码位置与改动建议,作为架构决策记录。
|
|
7
|
+
>
|
|
8
|
+
> **就绪语义加固(2026-08-26)**:用户主动连接时显式启用 `failOnStartupError`,必须等待 Host 完成首次 `initialize + tools/list` 后才持久化;健康检查与详情页改读 Host 实际注册的 `mcp__<serverName>__*` 工具。启动失败、进程退出或超时不再提前显示“已连接”。依赖默认值仍为 `false`,下文对应代码仅作为依赖能力说明。
|
|
7
9
|
> 状态:已完成(v0.2.12)
|
|
8
10
|
|
|
9
11
|
---
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -63,16 +63,19 @@ dsh web
|
|
|
63
63
|
| `连接` | OAuth 或免密连接器尚未连接 | 点击后按页面提示完成授权或连通性检查 |
|
|
64
64
|
| `配置` | 需要 Bearer Token 或 API Key | 录入服务商签发的凭据并验证 |
|
|
65
65
|
| `需重新授权` | OAuth 授权过期、被撤销或不可用 | 点击后重新完成 OAuth 授权 |
|
|
66
|
+
| `自动重试中` | OAuth Token 刷新遇到网络或服务端暂时故障 | 无需重新授权;保持 DSH 运行,插件会按退避策略自动恢复 |
|
|
66
67
|
| `已配置` | 本机已有配置,尚未完成本轮健康确认 | 点击“刷新”或进入详情检查 |
|
|
67
68
|
| `已连接` | 最近一次健康检查通过 | 可直接在会话中使用对应 MCP 工具 |
|
|
68
69
|
| `部分异常` / `连接异常` | 一个或多个 Server 未通过检查 | 打开详情查看提示,核对网络、权限和服务状态 |
|
|
69
70
|
|
|
70
71
|
Bearer/API Key 连接器会先执行 MCP `initialize` 验证。所有 Server 通过后,凭据才会保存并出现在“已安装”中;验证失败不会写入新凭据。
|
|
71
72
|
|
|
72
|
-
stdio 市场卡片也可能显示一个或多个凭据字段,例如 API Token、区域或租户标识。卡片目录只声明字段名称及其环境变量映射;提交后,真实值仅保存到 DSH 本机连接记录,并由插件注入该 stdio 进程的 `env
|
|
73
|
+
stdio 市场卡片也可能显示一个或多个凭据字段,例如 API Token、区域或租户标识。卡片目录只声明字段名称及其环境变量映射;提交后,真实值仅保存到 DSH 本机连接记录,并由插件注入该 stdio 进程的 `env`。市场、状态页和日志不会返回这些值。插件会等待 Host 完成首次 MCP 初始化与工具同步后再保存连接;失败或超时不会增加已安装数量,卡片也不会提前显示“已连接”。
|
|
73
74
|
|
|
74
75
|
OAuth 一键连接要求服务商支持标准 OAuth 2.1/PKCE 和公开元数据发现。动态客户端注册既支持无需客户端密钥的 `none`,也支持服务商签发密钥的 `client_secret_post` 与 `client_secret_basic`。客户端密钥仅与 OAuth Grant 一同保存在 DSH 本机,用于换取、刷新和撤销 Token;插件不会要求用户把 OAuth Token 或客户端密钥复制到聊天中。
|
|
75
76
|
|
|
77
|
+
当市场描述声明 `grantSharing: "issuer"` 时,同一账号下相同 issuer、scope 和客户端鉴权方式的卡片共享一组 Grant。首次授权仍只启用用户点击的卡片;之后连接同组卡片会直接复用现有授权,不再重复打开 OAuth 页面。网络、OAuth 元数据发现或服务端 5xx 等暂时故障只进入“自动重试中”,明确收到 Refresh Token/客户端失效错误时才进入“需重新授权”。
|
|
78
|
+
|
|
76
79
|
## 5. 查看详情、Prompt 与工具
|
|
77
80
|
|
|
78
81
|
点击卡片或“详情”可打开连接器详情:
|
|
@@ -121,7 +124,7 @@ OAuth 一键连接要求服务商支持标准 OAuth 2.1/PKCE 和公开元数据
|
|
|
121
124
|
- **HTTP**:填写名称、HTTPS MCP URL、可选 Header 和传输方式。
|
|
122
125
|
- **stdio**:填写本机命令、参数、环境变量和可选工作目录。
|
|
123
126
|
|
|
124
|
-
stdio 进程由 `@deepseek-ai/dsh-mcp-client`
|
|
127
|
+
stdio 进程由 `@deepseek-ai/dsh-mcp-client` 管理,插件透传 `command`、`args`、`env`、`cwd`,并等待 Host 完成首次 MCP 初始化和工具同步。stdio 会以当前用户权限启动本机进程,只运行你信任的软件包和命令。手动 HTTP 配置也会在保存前执行 `initialize` 校验;验证失败时表单内容仍保留,且不会生成无效连接记录。
|
|
125
128
|
|
|
126
129
|
### 6.3 市场卡片 URL
|
|
127
130
|
|
|
@@ -165,7 +168,7 @@ OAuth 断开时,插件会尽力调用服务商的撤销端点;无撤销端
|
|
|
165
168
|
|
|
166
169
|
### stdio 启动失败
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
页面会在有限时间内结束等待,并区分命令不存在、进程退出、初始化失败或启动超时。先在终端确认命令本身可执行、软件包可信、Node/运行时版本满足要求,并检查 `cwd`、参数和环境变量;随后查看 Host 日志并点击“重新检查”。不要把本机凭据写入公开 Registry descriptor。
|
|
169
172
|
|
|
170
173
|
### 如何反馈问题
|
|
171
174
|
|
|
@@ -80,12 +80,19 @@ export async function oauthAuthorize({ connector, config, logger, signal }) {
|
|
|
80
80
|
// 按 token 实际授权范围过滤 server;非 JWT / 无 resource claim 时 fallback 全部
|
|
81
81
|
const grantedUrls = extractTokenResources(token.accessToken);
|
|
82
82
|
let grantedKeys;
|
|
83
|
+
let grantedResources;
|
|
83
84
|
if (grantedUrls) {
|
|
84
85
|
const granted = new Set(grantedUrls);
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
const grantedServers = connector.servers.filter((s) => granted.has(s.url));
|
|
87
|
+
grantedKeys = grantedServers.map((s) => s.serverKey);
|
|
88
|
+
grantedResources = grantedServers.map((s) => s.url);
|
|
89
|
+
if (grantedKeys.length === 0) {
|
|
90
|
+
grantedKeys = [entryServer.serverKey];
|
|
91
|
+
grantedResources = [entryServer.url];
|
|
92
|
+
}
|
|
87
93
|
} else {
|
|
88
94
|
grantedKeys = connector.servers.map((s) => s.serverKey);
|
|
95
|
+
grantedResources = connector.servers.map((s) => s.url);
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
return {
|
|
@@ -98,6 +105,7 @@ export async function oauthAuthorize({ connector, config, logger, signal }) {
|
|
|
98
105
|
scope: connector.auth.scope,
|
|
99
106
|
token,
|
|
100
107
|
grantedKeys,
|
|
108
|
+
grantedResources,
|
|
101
109
|
entryResource,
|
|
102
110
|
};
|
|
103
111
|
}
|
package/lib/constants.js
CHANGED
|
@@ -27,7 +27,12 @@ export const DEFAULT_CATALOG_FALLBACK_URLS = [
|
|
|
27
27
|
|
|
28
28
|
/** 请求 / 刷新默认值 */
|
|
29
29
|
export const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
30
|
+
/** 首次启动 MCP Server 并完成 initialize + tools/list 的最长等待时间 */
|
|
31
|
+
export const DEFAULT_STARTUP_TIMEOUT_MS = 120_000;
|
|
30
32
|
export const DEFAULT_REFRESH_SKEW_MS = 300_000;
|
|
33
|
+
/** OAuth 刷新暂时失败后的指数退避范围。 */
|
|
34
|
+
export const DEFAULT_REFRESH_RETRY_BASE_MS = 30_000;
|
|
35
|
+
export const DEFAULT_REFRESH_RETRY_MAX_MS = 300_000;
|
|
31
36
|
export const DEFAULT_CATALOG_TTL_MS = 3_600_000;
|
|
32
37
|
|
|
33
38
|
/** PKCE 约束(RFC 7636) */
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** OAuth Grant 刷新失败分类、日志脱敏与重试退避。 */
|
|
2
|
+
|
|
3
|
+
const PERMANENT_REFRESH_CODES = new Set([
|
|
4
|
+
'invalid_grant',
|
|
5
|
+
'invalid_token',
|
|
6
|
+
'invalid_client',
|
|
7
|
+
'unauthorized_client',
|
|
8
|
+
'invalid_scope',
|
|
9
|
+
'client_secret_expired',
|
|
10
|
+
'missing_refresh_token',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
/** 防止 OAuth 服务端错误描述意外回显凭据。 */
|
|
14
|
+
export function redactOAuthDetail(value) {
|
|
15
|
+
return String(value ?? '')
|
|
16
|
+
.replace(/\bBearer\s+[^\s,;]+/gi, 'Bearer [REDACTED]')
|
|
17
|
+
.replace(/([?&](?:access_token|refresh_token|client_secret|code)=)[^&#\s]*/gi, '$1[REDACTED]')
|
|
18
|
+
.replace(/((?:access_token|refresh_token|client_secret)\s*[=:]\s*)[^\s,;&}]+/gi, '$1[REDACTED]')
|
|
19
|
+
.slice(0, 500);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 只有明确表示 Refresh Token / 动态客户端不可恢复的 OAuth 错误才要求重新授权。
|
|
24
|
+
* 网络、发现端点、5xx、超时和未知服务端错误都按暂时故障自动重试。
|
|
25
|
+
*/
|
|
26
|
+
export function classifyRefreshFailure(error) {
|
|
27
|
+
const code = typeof error?.code === 'string' && error.code
|
|
28
|
+
? redactOAuthDetail(error.code).slice(0, 80)
|
|
29
|
+
: error?.name === 'OAuthNetworkError' ? 'network_error' : 'refresh_failed';
|
|
30
|
+
const httpStatus = Number.isInteger(error?.httpStatus) ? error.httpStatus : undefined;
|
|
31
|
+
const permanent = PERMANENT_REFRESH_CODES.has(code) || httpStatus === 401 || httpStatus === 403;
|
|
32
|
+
return {
|
|
33
|
+
permanent,
|
|
34
|
+
kind: permanent ? 'permanent' : 'transient',
|
|
35
|
+
code,
|
|
36
|
+
httpStatus,
|
|
37
|
+
message: redactOAuthDetail(error?.message || code),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 指数退避:base、2×、4×……,封顶 max。failureCount 从 1 开始。 */
|
|
42
|
+
export function refreshRetryDelay(failureCount, baseMs, maxMs) {
|
|
43
|
+
const base = Math.max(1_000, Number(baseMs) || 30_000);
|
|
44
|
+
const max = Math.max(base, Number(maxMs) || 300_000);
|
|
45
|
+
const exponent = Math.max(0, Math.min(20, Number(failureCount || 1) - 1));
|
|
46
|
+
return Math.min(max, base * (2 ** exponent));
|
|
47
|
+
}
|