dsh-mcp-connector 0.2.9 → 0.2.11

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,28 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.11] - 2026-08-23
8
+
9
+ ### Added
10
+
11
+ - 新增八爪鱼云采集第三方市场卡片:通过标准 OAuth 2.1 Authorization Code + PKCE 和动态客户端注册完成一键授权,无需复制 API Key;精选 Prompt 覆盖模板搜索、任务进度、数据预览/导出,并在启动或停止云任务前要求用户确认。
12
+
13
+ ### Changed
14
+
15
+ - OAuth 发现按 RFC 8414 元数据优先、OIDC Discovery 补充的方式合并端点,兼容动态客户端注册与撤销端点分散发布的服务。
16
+ - 将 OAuth 撤销端点视为可选能力:服务未发布该端点时仍可授权、刷新和断开,断开时删除 DSH 本机授权记录。
17
+
18
+ ### Verification
19
+
20
+ - 75 项自动测试与 lint 通过;新增 OAuth/OIDC 元数据合并及无撤销端点兼容回归。
21
+ - 八爪鱼公开 MCP 端点、Protected Resource Metadata、Authorization Server Metadata、OIDC Discovery、PKCE S256 与动态客户端注册端点探针通过;真实账户授权和工具调用留待 Desktop 用户登录验收。
22
+
23
+ ## [0.2.10] - 2026-08-22
24
+
25
+ ### Fixed
26
+
27
+ - 工具发现遇到瞬时网络断开或 MCP 5xx 时自动重试一次;鉴权、协议、4xx 和超时仍直接返回,减少 QVeris 等远程服务偶发 TLS reset 导致的连接状态误报。
28
+
7
29
  ## [0.2.9] - 2026-08-22
8
30
 
9
31
  ### Added
@@ -194,7 +216,9 @@
194
216
  - 外部 URL 与导入 Header 执行安全校验。
195
217
  - iframe 消息校验同源和消息来源。
196
218
 
197
- [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.9...HEAD
219
+ [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.11...HEAD
220
+ [0.2.11]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.10...v0.2.11
221
+ [0.2.10]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.9...v0.2.10
198
222
  [0.2.9]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.8...v0.2.9
199
223
  [0.2.8]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.7...v0.2.8
200
224
  [0.2.7]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.6...v0.2.7
package/README.en.md CHANGED
@@ -26,7 +26,7 @@ Browse and install MCP connectors from different providers in DeepSeek Harness D
26
26
  - A standalone remote Registry, allowing new marketplace cards to appear after refresh without publishing a new npm version.
27
27
  - Explicit, non-destructive migration of authorization from the two earlier Qichacha OAuth plugins.
28
28
 
29
- The first four bundled cards are Qichacha connectors, followed by PKULaw and Wind. After a remote Registry refresh, Yingmi and QVeris are also available, for eight published marketplace cards in total; the architecture is provider-neutral.
29
+ The first four bundled cards are Qichacha connectors, followed by PKULaw and Wind. After a remote Registry refresh, Yingmi, QVeris, and Bazhuayu Cloud Collection are also available, for nine published marketplace cards in total. Bazhuayu is the first third-party example to exercise the complete OAuth 2.1 + PKCE dynamic-registration flow; the architecture remains provider-neutral.
30
30
 
31
31
  ## Interface and demo
32
32
 
@@ -100,7 +100,7 @@ npm run dev:ui
100
100
 
101
101
  `npm run check` performs syntax checks, automated tests, and an npm package allowlist/sensitive-content audit. `npm run market:check` tracks the external DSH marketplace PR and live directory. 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`.
102
102
 
103
- The current public version is [`dsh-mcp-connector@0.2.7`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.7](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.7).
103
+ The current public version is [`dsh-mcp-connector@0.2.11`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.11](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.11).
104
104
 
105
105
  See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
106
106
 
package/README.md CHANGED
@@ -28,7 +28,7 @@
28
28
  - 平滑迁移:显式扫描并复制两个旧企查查 OAuth 插件授权;不删除原插件或原凭据。
29
29
  - 对话工具:`mcp_connector_catalog`、`connect`、`configure`、`import_json`、`install_from_url`、`status`、`health_check`、`set_enabled`、`disconnect`、`refresh_catalog`、`publish`、`tools_list`。
30
30
 
31
- 随包目录前 4 张为企查查连接器,另含“北大法宝·法律检索”和“Wind·股票数据”。远程 Registry 刷新后还会加入“盈米·基金投顾”和“QVerisMCP”,当前共 8 张已发布市场卡片;插件架构不限定厂商。
31
+ 随包目录前 4 张为企查查连接器,另含“北大法宝·法律检索”和“Wind·股票数据”。远程 Registry 刷新后还会加入“盈米·基金投顾”、“QVerisMCP”和“八爪鱼·云采集”,当前共 9 张已发布市场卡片;其中八爪鱼是首个完整跑通标准 OAuth 2.1 + PKCE 动态注册链路的第三方示例,插件架构不限定厂商。
32
32
 
33
33
  ## 界面与演示
34
34
 
@@ -96,7 +96,7 @@ npm run dev:ui
96
96
 
97
97
  `check` 执行语法检查、自动测试和 npm 发布包白名单校验;`market:check` 检查外部 DSH 市场 PR 与线上目录;`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`。
98
98
 
99
- 当前公开版本为 [`dsh-mcp-connector@0.2.7`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.7](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.7)。
99
+ 当前公开版本为 [`dsh-mcp-connector@0.2.11`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.11](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.11)。
100
100
 
101
101
  版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
102
102
  Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
@@ -45,9 +45,11 @@ Bearer/API Key 型连接器可在卡片上点「配置」,一次填写凭据
45
45
  - OAuth Authorization Code + PKCE S256;
46
46
  - MCP Protected Resource Metadata(RFC 9728);
47
47
  - Authorization Server Metadata(RFC 8414);
48
- - `authorization_endpoint`、`token_endpoint`、`registration_endpoint`、`revocation_endpoint`;
48
+ - `authorization_endpoint`、`token_endpoint`、`registration_endpoint`;`revocation_endpoint` 建议提供,但按标准属于可选能力;
49
49
  - Dynamic Client Registration,且支持 loopback callback URI;
50
- - Refresh Token 和撤销。
50
+ - Refresh Token;如提供撤销端点,插件会在断开/清理授权时撤销 Refresh Token,否则只删除 DSH 本机授权记录。
51
+
52
+ 插件优先读取 RFC 8414 Authorization Server Metadata;若动态注册或撤销端点缺失,会再读取 OIDC Discovery 并仅补齐缺失字段。OAuth 元数据中的授权、Token 等标准端点始终优先。
51
53
 
52
54
  符合上述条件的最小描述:
53
55
 
@@ -153,3 +155,25 @@ QVeris Hosted MCP 提供单一 Streamable HTTP 端点 `https://mcp.qveris.ai/mcp
153
155
  - https://qveris.ai/pricing
154
156
  - https://qveris.ai/privacy
155
157
  - https://qveris.ai/terms
158
+
159
+ ## 9. 八爪鱼云采集 OAuth 适配结论
160
+
161
+ 八爪鱼公开 MCP 服务 `https://mcp.bazhuayu.com/` 已验证支持标准远程 MCP OAuth 发现链路:
162
+
163
+ - Protected Resource Metadata 指向 `https://identity.bazhuayu.com`;
164
+ - Authorization Code + PKCE S256;
165
+ - Dynamic Client Registration,公共客户端使用 `token_endpoint_auth_method: none`;
166
+ - Scope:`openid profile offline_access`;
167
+ - OAuth Metadata 发布授权与 Token 端点,OIDC Discovery 补充动态注册和撤销端点。
168
+
169
+ 因此可以作为本市场首个完整的第三方 OAuth 一键授权示例。连接器不复用 WorkBuddy 的 `client_id` 或私有回调协议,而是由 DSH 插件为本机 loopback callback 动态注册独立公共客户端;授权结果只保存在 DSH storage domain。
170
+
171
+ 市场卡片使用八爪鱼官网 favicon 的像素副本并由 Registry 自托管;能力描述与 10 个工具快照以官方 MCP 文档为准。涉及启动/停止云任务的 Prompt 必须先征求用户确认,实际云采集配额、费用、平台范围与数据使用责任以用户账户和八爪鱼官方规则为准。
172
+
173
+ 参考:
174
+
175
+ - https://www.bazhuayu.com/ai-open-platform
176
+ - https://www.bazhuayu.com/docs/zh/mcp
177
+ - https://mcp.bazhuayu.com/.well-known/oauth-protected-resource
178
+ - https://identity.bazhuayu.com/.well-known/oauth-authorization-server
179
+ - https://identity.bazhuayu.com/.well-known/openid-configuration
package/lib/index.js CHANGED
@@ -201,11 +201,13 @@ export async function apply(ctx, config) {
201
201
  if (revoke && entry.grant.refreshToken) {
202
202
  try {
203
203
  const metadata = await discoverServerMetadata(entry.grant.issuer, config.requestTimeoutMs);
204
- await revokeRefreshToken(metadata.revocationEndpoint, {
205
- clientId: entry.grant.clientId,
206
- refreshToken: entry.grant.refreshToken,
207
- timeoutMs: config.requestTimeoutMs,
208
- });
204
+ if (metadata.revocationEndpoint) {
205
+ await revokeRefreshToken(metadata.revocationEndpoint, {
206
+ clientId: entry.grant.clientId,
207
+ refreshToken: entry.grant.refreshToken,
208
+ timeoutMs: config.requestTimeoutMs,
209
+ });
210
+ }
209
211
  } catch (error) {
210
212
  logger.warn(`retire unused grant ${grantKey}: revoke failed, removing local copy: ${error.message}`);
211
213
  }
@@ -716,11 +718,13 @@ export async function apply(ctx, config) {
716
718
  if (g?.grant.refreshToken) {
717
719
  try {
718
720
  const metadata = await discoverServerMetadata(g.grant.issuer, config.requestTimeoutMs);
719
- await revokeRefreshToken(metadata.revocationEndpoint, {
720
- clientId: g.grant.clientId,
721
- refreshToken: g.grant.refreshToken,
722
- timeoutMs: config.requestTimeoutMs,
723
- });
721
+ if (metadata.revocationEndpoint) {
722
+ await revokeRefreshToken(metadata.revocationEndpoint, {
723
+ clientId: g.grant.clientId,
724
+ refreshToken: g.grant.refreshToken,
725
+ timeoutMs: config.requestTimeoutMs,
726
+ });
727
+ }
724
728
  } catch (error) {
725
729
  logger.warn(`revoke failed (continuing disconnect): ${error.message}`);
726
730
  }
package/lib/mcp-http.js CHANGED
@@ -115,7 +115,7 @@ async function closeSession(record, { grants, sessionId, protocolVersion, fetchI
115
115
  } catch {}
116
116
  }
117
117
 
118
- export async function listMcpTools(record, {
118
+ async function listMcpToolsOnce(record, {
119
119
  timeoutMs = 15_000,
120
120
  fetchImpl = fetch,
121
121
  grants = new Map(),
@@ -192,3 +192,44 @@ export async function listMcpTools(record, {
192
192
  await closeSession(record, { grants, sessionId, protocolVersion, fetchImpl });
193
193
  }
194
194
  }
195
+
196
+ function errorChain(error) {
197
+ const parts = [];
198
+ let current = error;
199
+ for (let depth = 0; current && depth < 4; depth += 1) {
200
+ parts.push(current.code, current.name, current.message);
201
+ current = current.cause;
202
+ }
203
+ return parts.filter(Boolean).join(' ');
204
+ }
205
+
206
+ function isRetryableDiscoveryError(error) {
207
+ if (error instanceof McpHttpError) {
208
+ return error.kind === 'http' && Number(error.status) >= 500;
209
+ }
210
+ const text = errorChain(error);
211
+ if (/AbortError|timeout|timed out|ETIMEDOUT/i.test(text)) return false;
212
+ return /ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|fetch failed|socket disconnected|connection reset|network error/i.test(text);
213
+ }
214
+
215
+ /**
216
+ * 获取完整工具目录。瞬时网络错误或 5xx 最多自动重试一次;鉴权、协议、
217
+ * 4xx 和超时不会重试,避免掩盖无效凭据或延长确定性失败。
218
+ */
219
+ export async function listMcpTools(record, options = {}) {
220
+ const retryCount = Math.max(0, Math.min(Number(options.retryCount ?? 1), 2));
221
+ const retryDelayMs = Math.max(0, Math.min(Number(options.retryDelayMs ?? 250), 2_000));
222
+ const requestOptions = { ...options };
223
+ delete requestOptions.retryCount;
224
+ delete requestOptions.retryDelayMs;
225
+
226
+ for (let attempt = 0; ; attempt += 1) {
227
+ try {
228
+ const result = await listMcpToolsOnce(record, requestOptions);
229
+ return { ...result, attempts: attempt + 1 };
230
+ } catch (error) {
231
+ if (attempt >= retryCount || !isRetryableDiscoveryError(error)) throw error;
232
+ if (retryDelayMs > 0) await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
233
+ }
234
+ }
235
+ }
package/lib/oauth.js CHANGED
@@ -142,20 +142,38 @@ export async function discoverProtectedResource(streamUrl, timeoutMs) {
142
142
 
143
143
  export async function discoverServerMetadata(issuer, timeoutMs) {
144
144
  assertSafeUrl(issuer);
145
- const url = `${issuer.replace(/\/+$/, '')}/.well-known/oauth-authorization-server`;
146
- const body = await fetchJson(url, { method: 'GET' }, timeoutMs);
147
- for (const field of ['authorization_endpoint', 'token_endpoint', 'registration_endpoint', 'revocation_endpoint']) {
145
+ const base = issuer.replace(/\/+$/, '');
146
+ const oauthUrl = `${base}/.well-known/oauth-authorization-server`;
147
+ const oauthBody = await fetchJson(oauthUrl, { method: 'GET' }, timeoutMs);
148
+
149
+ // 一些兼容 OIDC 的服务会把 DCR / token revoke 端点只发布在
150
+ // openid-configuration 中。优先使用 RFC 8414 元数据,仅在扩展端点缺失时合并 OIDC 元数据。
151
+ let oidcBody = {};
152
+ if (!oauthBody.registration_endpoint || !oauthBody.revocation_endpoint) {
153
+ try {
154
+ oidcBody = await fetchJson(`${base}/.well-known/openid-configuration`, { method: 'GET' }, timeoutMs);
155
+ } catch {
156
+ // OIDC 发现是可选兼容路径;OAuth-only 服务不应因此失败。
157
+ }
158
+ }
159
+ const body = { ...oidcBody, ...oauthBody };
160
+
161
+ // 当前一键 OAuth 流程依赖动态客户端注册;撤销端点则是 RFC 8414 的可选元数据。
162
+ for (const field of ['authorization_endpoint', 'token_endpoint', 'registration_endpoint']) {
148
163
  if (typeof body[field] !== 'string' || body[field].length === 0) {
149
164
  throw new OAuthError('invalid_metadata', `oauth server metadata missing ${field}`);
150
165
  }
151
166
  assertSafeUrl(body[field]);
152
167
  }
168
+ if (typeof body.revocation_endpoint === 'string' && body.revocation_endpoint.length > 0) {
169
+ assertSafeUrl(body.revocation_endpoint);
170
+ }
153
171
  return {
154
172
  issuer: body.issuer ?? issuer,
155
173
  authorizationEndpoint: body.authorization_endpoint,
156
174
  tokenEndpoint: body.token_endpoint,
157
175
  registrationEndpoint: body.registration_endpoint,
158
- revocationEndpoint: body.revocation_endpoint,
176
+ revocationEndpoint: body.revocation_endpoint || undefined,
159
177
  responseTypesSupported: body.response_types_supported ?? [],
160
178
  grantTypesSupported: body.grant_types_supported ?? [],
161
179
  codeChallengeMethodsSupported: body.code_challenge_methods_supported ?? [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mcp-connector",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
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",