dsh-mcp-connector 0.2.4 → 0.2.6

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 CHANGED
@@ -4,6 +4,34 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.6] - 2026-08-21
8
+
9
+ ### Documentation
10
+
11
+ - 记录 `0.2.5` GitHub/npm 发布、DSH Desktop 精确版本安装、孤立 grant 自动清理与 19/19 Server 健康回归结果。
12
+ - 扩充 GitHub Topic、仓库简介、README 和 npm 关键词,覆盖 MCP连接器、连接管理、插件/扩展、集成、Qichacha/QCC 和企查查等真实检索入口。
13
+ - 明确“技能扩展”指 MCP 工具与 Prompt 对智能体能力的扩展,不将本包标记为独立 DSH Skill。
14
+
15
+ ## [0.2.5] - 2026-08-21
16
+
17
+ ### Changed
18
+
19
+ - 同一连接器已有 OAuth 授权流程进行中时,后续重复点击复用同一 Promise,不再重复打开授权页或创建重复 grant。
20
+
21
+ ### Security
22
+
23
+ - OAuth 重新授权成功后,撤销并删除已不再被连接引用的旧 refresh token/grant;插件启动时同步清理历史孤立 grant,仅保留当前连接仍引用的本机凭据。
24
+ - 授权流程中途失败时清理未被连接引用的临时 grant,避免失败重试累积敏感记录。
25
+
26
+ ### Fixed
27
+
28
+ - 修复连续或并发 OAuth 重新授权后,本机可能残留重复且未被连接引用的授权记录。
29
+
30
+ ### Documentation
31
+
32
+ - 记录 `0.2.4` GitHub/npm 发布、DSH Desktop 精确版本安装和连接健康状态实机回归结果。
33
+ - 记录旧企查查 OAuth 插件凭据迁移的用户确认、幂等执行和源数据保留验证结果。
34
+
7
35
  ## [0.2.4] - 2026-08-21
8
36
 
9
37
  ### Added
@@ -119,7 +147,9 @@
119
147
  - 外部 URL 与导入 Header 执行安全校验。
120
148
  - iframe 消息校验同源和消息来源。
121
149
 
122
- [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.4...HEAD
150
+ [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.6...HEAD
151
+ [0.2.6]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.5...v0.2.6
152
+ [0.2.5]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.4...v0.2.5
123
153
  [0.2.4]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.3...v0.2.4
124
154
  [0.2.3]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.2...v0.2.3
125
155
  [0.2.2]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.1...v0.2.2
package/README.en.md CHANGED
@@ -1,8 +1,10 @@
1
- # MCP Connector Marketplace for DeepSeek Harness
1
+ # MCP Connector and Connection Manager Marketplace for DeepSeek Harness
2
2
 
3
- > A general-purpose MCP connector marketplace initiated and maintained by the Qichacha team
3
+ > A general-purpose MCP connector, plugin extension, and integration marketplace initiated and maintained by the Qichacha (QCC) team
4
4
 
5
- Browse and install MCP connectors from different providers in DeepSeek Harness Desktop. Connect through OAuth, API key/URL configuration, JSON import, or a connector descriptor URL; discover tools, start a new conversation from an example prompt, and manage installed connections.
5
+ Browse and install MCP connectors from different providers in DeepSeek Harness Desktop. Connect MCP servers through OAuth, API key/URL configuration, JSON import, or a connector descriptor URL; discover tools and prompts, extend agent skills, start a new conversation, and manage installed connections.
6
+
7
+ > Here, “skill extension” means extending an agent through MCP tools and prompts; this package does not present itself as a standalone DSH Skill.
6
8
 
7
9
  [简体中文](README.md)
8
10
 
@@ -97,7 +99,7 @@ npm run dev:ui
97
99
 
98
100
  `npm run check` performs syntax checks, automated tests, and an npm package allowlist/sensitive-content audit. Tags matching `v*` trigger GitHub Actions; the tag must match `package.json`. npm releases use Trusted Publishing through GitHub OIDC and do not require a long-lived `NPM_TOKEN`.
99
101
 
100
- The current public version is [`dsh-mcp-connector@0.2.4`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.4](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.4).
102
+ The current public version is [`dsh-mcp-connector@0.2.6`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.6](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.6).
101
103
 
102
104
  See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
103
105
 
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # MCP 连接器市场(DeepSeek Harness 插件)
1
+ # MCP连接器与连接管理市场(DeepSeek Harness 插件 / 扩展)
2
2
 
3
- > 通用 MCP Connector Marketplace,由企查查团队发起并维护
3
+ > 通用 MCP Connector / Connection Manager / Integration Marketplace,由企查查(Qichacha/QCC)团队发起并维护
4
4
 
5
- 在 DeepSeek Harness Desktop 中浏览和安装不同厂商的 MCP 连接器,通过 OAuth、API Key/URL 或 JSON 接入服务,发现工具、使用示例 Prompt 发起新会话,并管理已安装连接。
5
+ 在 DeepSeek Harness Desktop 中浏览和安装不同厂商的 MCP连接器,连接 MCP Server,通过 OAuth、API Key/URL 或 JSON 接入服务,发现工具与 Prompt,扩展 AI 技能,并管理已安装连接。
6
+
7
+ > 注:“技能扩展”指通过 MCP 工具和 Prompt 扩展智能体能力,本包不会伪装成独立 DSH Skill。
6
8
 
7
9
  [English](README.en.md)
8
10
 
@@ -93,7 +95,7 @@ npm run dev:ui
93
95
 
94
96
  `check` 执行语法检查、自动测试和 npm 发布包白名单校验;`dev:ui` 启动不含真实凭据的本地 mock 市场。CI 使用 `--legacy-peer-deps` 安装显式测试依赖,DSH 运行期 peer 仍由 Host 提供。`v*` Tag 会触发 GitHub Actions;Tag 必须与 `package.json` 版本一致。Release 通过 npm Trusted Publishing (GitHub OIDC) 发布,不依赖长期 `NPM_TOKEN`。
95
97
 
96
- 当前公开版本为 [`dsh-mcp-connector@0.2.4`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.4](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.4)。
98
+ 当前公开版本为 [`dsh-mcp-connector@0.2.6`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.6](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.6)。
97
99
 
98
100
  版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
99
101
  Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
package/lib/index.js CHANGED
@@ -78,6 +78,7 @@ export async function apply(ctx, config) {
78
78
  connections: new Map(), // key -> ConnectionRecord
79
79
  grants: new Map(), // grantKey -> { grant, timer, refreshPromise, needsReauth }
80
80
  health: new Map(), // connectorId -> 最近一次主动连通性检查摘要(不持久化)
81
+ oauthConnects: new Map(), // connectorId -> 正在进行的 OAuth 授权 Promise(防重复弹窗/重复 grant)
81
82
  };
82
83
 
83
84
  /* ───────────────────────── 目录加载 ───────────────────────── */
@@ -192,6 +193,36 @@ export async function apply(ctx, config) {
192
193
  entry.timer.unref?.();
193
194
  }
194
195
 
196
+ async function retireGrantIfUnused(grantKey, { revoke = false } = {}) {
197
+ if ([...state.connections.values()].some((record) => record.auth?.grantKey === grantKey)) return false;
198
+ const entry = state.grants.get(grantKey);
199
+ if (!entry) return false;
200
+ if (revoke && entry.grant.refreshToken) {
201
+ try {
202
+ const metadata = await discoverServerMetadata(entry.grant.issuer, config.requestTimeoutMs);
203
+ await revokeRefreshToken(metadata.revocationEndpoint, {
204
+ clientId: entry.grant.clientId,
205
+ refreshToken: entry.grant.refreshToken,
206
+ timeoutMs: config.requestTimeoutMs,
207
+ });
208
+ } catch (error) {
209
+ logger.warn(`retire unused grant ${grantKey}: revoke failed, removing local copy: ${error.message}`);
210
+ }
211
+ }
212
+ if (entry.timer) clearTimeout(entry.timer);
213
+ state.grants.delete(grantKey);
214
+ if (config.persistSecrets) await grantStore.delete(grantKey).catch(() => {});
215
+ return true;
216
+ }
217
+
218
+ async function pruneOrphanGrants({ revoke = false } = {}) {
219
+ let removed = 0;
220
+ for (const grantKey of [...state.grants.keys()]) {
221
+ if (await retireGrantIfUnused(grantKey, { revoke })) removed += 1;
222
+ }
223
+ return removed;
224
+ }
225
+
195
226
  /* ───────────────────────── 连接记录落库 / 挂载 ───────────────────────── */
196
227
 
197
228
  async function persistRecord(record) {
@@ -323,58 +354,82 @@ export async function apply(ctx, config) {
323
354
  if (!connector) return { ok: false, message: `连接器 "${connectorId}" 不存在(可先 mcp_connector_refresh_catalog 或 mcp_connector_catalog 查看)` };
324
355
 
325
356
  if (connector.auth.mode === 'oauth2-pkce') {
326
- try {
327
- const authz = await oauthAuthorize({ connector, config, logger, signal });
328
- const grantKey = grantStore.keyFor(config.account, authz.issuer, authz.clientId, authz.scope);
329
- const entryResource = authz.entryResource;
330
- const grant = {
331
- key: grantKey,
332
- issuer: authz.issuer,
333
- clientId: authz.clientId,
334
- clientName: authz.clientName,
335
- scope: authz.scope,
336
- account: config.account,
337
- accessToken: authz.token.accessToken,
338
- accessTokenExpiresAt: Date.now() + authz.token.expiresIn * 1000 - config.refreshSkewMs,
339
- refreshToken: authz.token.refreshToken ?? '',
340
- authorizedResources: connector.servers
341
- .filter((s) => authz.grantedKeys.includes(s.serverKey))
342
- .map((s) => s.url).length
343
- ? connector.servers.filter((s) => authz.grantedKeys.includes(s.serverKey)).map((s) => s.url)
344
- : [entryResource],
345
- connectorIds: [connector.id],
346
- updatedAt: Date.now(),
347
- };
348
- state.grants.set(grantKey, { grant, timer: undefined, refreshPromise: null, needsReauth: false });
349
- if (config.persistSecrets) await grantStore.put(grant);
350
-
351
- const created = [];
352
- for (const sk of authz.grantedKeys) {
353
- const server = connector.servers.find((s) => s.serverKey === sk);
354
- if (!server) continue;
355
- const record = {
356
- key: `${connector.id}-${sk}`,
357
- connectorId: connector.id,
358
- kind: 'oauth',
359
- name: connector.servers.length > 1 ? `${connector.name}·${sk}` : connector.name,
360
- serverKey: sk,
361
- transport: server.transport,
362
- url: server.url,
363
- serverName: server.serverName,
364
- headers: server.headers,
365
- auth: { mode: 'oauth', grantKey },
366
- enabled: true,
367
- createdAt: Date.now(),
357
+ const inFlight = state.oauthConnects.get(connector.id);
358
+ if (inFlight) return inFlight;
359
+ const promise = (async () => {
360
+ try {
361
+ const previousGrantKeys = new Set(
362
+ [...state.grants]
363
+ .filter(([, entry]) => entry.grant.connectorIds?.includes(connector.id))
364
+ .map(([key]) => key),
365
+ );
366
+ const authz = await oauthAuthorize({ connector, config, logger, signal });
367
+ const grantKey = grantStore.keyFor(config.account, authz.issuer, authz.clientId, authz.scope);
368
+ const entryResource = authz.entryResource;
369
+ const grant = {
370
+ key: grantKey,
371
+ issuer: authz.issuer,
372
+ clientId: authz.clientId,
373
+ clientName: authz.clientName,
374
+ scope: authz.scope,
375
+ account: config.account,
376
+ accessToken: authz.token.accessToken,
377
+ accessTokenExpiresAt: Date.now() + authz.token.expiresIn * 1000 - config.refreshSkewMs,
378
+ refreshToken: authz.token.refreshToken ?? '',
379
+ authorizedResources: connector.servers
380
+ .filter((s) => authz.grantedKeys.includes(s.serverKey))
381
+ .map((s) => s.url).length
382
+ ? connector.servers.filter((s) => authz.grantedKeys.includes(s.serverKey)).map((s) => s.url)
383
+ : [entryResource],
384
+ connectorIds: [connector.id],
368
385
  updatedAt: Date.now(),
369
386
  };
370
- await upsertRecord(record);
371
- created.push(record.key);
387
+ state.grants.set(grantKey, { grant, timer: undefined, refreshPromise: null, needsReauth: false });
388
+ if (config.persistSecrets) await grantStore.put(grant);
389
+
390
+ const created = [];
391
+ for (const sk of authz.grantedKeys) {
392
+ const server = connector.servers.find((s) => s.serverKey === sk);
393
+ if (!server) continue;
394
+ const record = {
395
+ key: `${connector.id}-${sk}`,
396
+ connectorId: connector.id,
397
+ kind: 'oauth',
398
+ name: connector.servers.length > 1 ? `${connector.name}·${sk}` : connector.name,
399
+ serverKey: sk,
400
+ transport: server.transport,
401
+ url: server.url,
402
+ serverName: server.serverName,
403
+ headers: server.headers,
404
+ auth: { mode: 'oauth', grantKey },
405
+ enabled: true,
406
+ createdAt: Date.now(),
407
+ updatedAt: Date.now(),
408
+ };
409
+ await upsertRecord(record);
410
+ created.push(record.key);
411
+ }
412
+ scheduleRefresh(grantKey);
413
+ let retiredGrantCount = 0;
414
+ for (const previousKey of previousGrantKeys) {
415
+ if (previousKey !== grantKey && await retireGrantIfUnused(previousKey, { revoke: true })) retiredGrantCount += 1;
416
+ }
417
+ return {
418
+ ok: true,
419
+ message: `已连接 "${connector.name}"(${created.length} 个 MCP server),共 ${created.length} 条`,
420
+ detail: { keys: created, grantKey, retiredGrantCount },
421
+ };
422
+ } catch (error) {
423
+ logger.error(`oauth connect failed: ${error.message}`);
424
+ await pruneOrphanGrants();
425
+ return { ok: false, message: `连接失败: ${error.message}` };
372
426
  }
373
- scheduleRefresh(grantKey);
374
- return { ok: true, message: `已连接 "${connector.name}"(${created.length} 个 MCP server),共 ${created.length} 条`, detail: { keys: created, grantKey } };
375
- } catch (error) {
376
- logger.error(`oauth connect failed: ${error.message}`);
377
- return { ok: false, message: `连接失败: ${error.message}` };
427
+ })();
428
+ state.oauthConnects.set(connector.id, promise);
429
+ try {
430
+ return await promise;
431
+ } finally {
432
+ if (state.oauthConnects.get(connector.id) === promise) state.oauthConnects.delete(connector.id);
378
433
  }
379
434
  }
380
435
 
@@ -845,6 +900,9 @@ export async function apply(ctx, config) {
845
900
  }
846
901
  }
847
902
 
903
+ const prunedGrantCount = await pruneOrphanGrants();
904
+ if (prunedGrantCount > 0) logger.info(`removed ${prunedGrantCount} unreferenced OAuth grant(s) from local storage`);
905
+
848
906
  for (const [grantKey, g] of state.grants) {
849
907
  if (g.grant.accessTokenExpiresAt <= Date.now()) {
850
908
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-mcp-connector",
3
- "version": "0.2.4",
4
- "description": "General-purpose MCP Connector Marketplace for DeepSeek Harness with OAuth 2.0 PKCE, API key/URL, JSON import, tool discovery, prompt templates, and connection management.",
3
+ "version": "0.2.6",
4
+ "description": "General-purpose MCP connector, connection manager, plugin extension, and integration marketplace for DeepSeek Harness, initiated and maintained by Qichacha/QCC.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -46,18 +46,36 @@
46
46
  "cordis",
47
47
  "mcp",
48
48
  "mcp-client",
49
+ "mcp-server",
49
50
  "mcp-connector",
51
+ "mcp-marketplace",
50
52
  "connector",
53
+ "connection",
54
+ "connection-manager",
51
55
  "marketplace",
56
+ "plugin",
57
+ "extension",
58
+ "integration",
59
+ "agent-skills",
60
+ "skill-extension",
61
+ "ai-tools",
52
62
  "oauth",
53
63
  "oauth2",
54
64
  "pkce",
65
+ "api-key",
55
66
  "json-import",
56
67
  "enterprise-data",
57
68
  "qichacha",
58
69
  "qcc",
59
70
  "tools",
60
- "prompts"
71
+ "prompts",
72
+ "MCP连接器",
73
+ "连接器",
74
+ "连接管理",
75
+ "技能扩展",
76
+ "插件",
77
+ "扩展",
78
+ "企查查"
61
79
  ],
62
80
  "license": "MIT",
63
81
  "publishConfig": {