dsh-mcp-connector 0.2.49 → 0.2.50

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,20 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.50] - 2026-09-16
8
+
9
+ ### Fixed
10
+
11
+ - 修复 #79:含路径的 OAuth issuer 按 RFC 8414 在主机与路径之间插入发现地址;标准地址返回 404/405 时兼容旧后缀地址,无路径行为不变。
12
+ - OIDC 补充发现兼容插入式和历史后缀式地址;仅合并 issuer 精确一致的元数据,拒绝缺失/不匹配的 OAuth issuer 及带 query/fragment 的 issuer,不因 403/429/5xx 或网络故障转试旧地址。
13
+ - 区分 CLI 未授权与状态响应异常,保留可识别的诊断标记;Host 未保留具体原因时明确标记原因未知,不猜测管理员限制。
14
+ - 不再因错误包含“连接”二字自动追加 MCP URL 建议;CLI/OAuth 错误保留原有指引。
15
+
16
+ ### Verification
17
+
18
+ - 新增 14 项 OAuth 发现回归;完整 279 项测试通过,7 张商店截图和 78 个发布文件检查通过。
19
+ - 修复后只读发现 Espressif docs 的公开 OAuth 元数据成功;未创建客户端、未进行用户授权或调用工具。本版不包含仍在独立 PR 中的钉钉浏览器授权新流程。
20
+
7
21
  ## [0.2.49] - 2026-09-15
8
22
 
9
23
  ### Fixed
package/README.en.md CHANGED
@@ -190,7 +190,7 @@ npm run dev:ui
190
190
 
191
191
  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.
192
192
 
193
- The current public version is [`dsh-mcp-connector@0.2.49`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.49](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.49).
193
+ The current public version is [`dsh-mcp-connector@0.2.50`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.50](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.50).
194
194
 
195
195
  See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
196
196
 
package/README.md CHANGED
@@ -185,7 +185,7 @@ npm run dev:ui
185
185
 
186
186
  公共 Registry 每次合并后会生成 `catalog-stats.json`;本仓库的定时工作流每小时同步中英文介绍和统计快照。npm 页面中的静态正文随版本发布更新,上方动态统计徽标则直接读取 Registry,可在不发布新 npm 版本时保持实时数量一致。
187
187
 
188
- 当前公开版本为 [`dsh-mcp-connector@0.2.49`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.49](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.49)。
188
+ 当前公开版本为 [`dsh-mcp-connector@0.2.50`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.50](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.50)。
189
189
 
190
190
  版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
191
191
  Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
@@ -385,11 +385,14 @@ export async function ensureCliProviderReady(providerId, options = {}) {
385
385
  try {
386
386
  status = JSON.parse(output);
387
387
  } catch {
388
- throw new Error('无法验证钉钉 OAuth 状态:官方 CLI 未返回有效 JSON;请检查 CLI 版本后重试');
388
+ throw new Error('[CLI_AUTH_STATUS_INVALID] 无法验证钉钉 OAuth 状态:官方 CLI 未返回有效 JSON;请检查 CLI 版本后重试');
389
+ }
390
+ if (status?.success === true && status?.authenticated === false) {
391
+ throw new Error('[CLI_AUTH_REQUIRED] 钉钉待授权:官方 CLI 当前未登录。具备企业授权条件后,在运行 DSH 的同一系统用户终端执行 npx -y dingtalk-workspace-cli@1.0.61 auth login。若已确认企业未开放 CLI,请等待管理员处理,无需反复登录、重启或填写 API Key;仅凭未登录状态不能判断管理员是否限制');
389
392
  }
390
393
  if (!status || typeof status !== 'object' || Array.isArray(status)
391
394
  || status.success !== true || status.authenticated !== true) {
392
- throw new Error('钉钉 OAuth 未就绪:请先在运行 DSH 的同一电脑、同一系统用户下执行 npx -y dingtalk-workspace-cli@1.0.61 auth login(无浏览器环境加 --device),完成授权后重新连接;如仍失败,请确认企业管理员已开启 CLI 访问');
395
+ throw new Error('[CLI_AUTH_STATUS_INVALID] 无法确认钉钉授权状态:官方 CLI 返回失败或不完整的状态响应;请核对 CLI 诊断。不能据此判断为未登录或管理员限制');
393
396
  }
394
397
  }
395
398
 
@@ -57,6 +57,12 @@ function errorChain(error) {
57
57
 
58
58
  export function classifyConnectionError(error, { transport } = {}) {
59
59
  const text = errorChain(error);
60
+ if (text.includes('[CLI_AUTH_REQUIRED]')) {
61
+ return { kind: 'authorization-required', message: '钉钉待授权:官方 CLI 当前未登录。若已确认企业尚未开放 CLI,请等待管理员处理,无需反复登录、重启或填写 API Key;具备授权条件后再完成官方登录并连接' };
62
+ }
63
+ if (text.includes('[CLI_AUTH_STATUS_INVALID]')) {
64
+ return { kind: 'authorization-unknown', message: '钉钉授权状态未知:官方 CLI 返回异常或不完整响应,请检查 CLI 诊断;不能据此判断为未登录或管理员限制' };
65
+ }
60
66
  if (/AbortError|timeout|timed out|ETIMEDOUT/i.test(text)) {
61
67
  return {
62
68
  kind: 'timeout',
@@ -86,7 +92,9 @@ export function classifyConnectionError(error, { transport } = {}) {
86
92
  };
87
93
  }
88
94
  if (/initial (?:connection|tool synchronization)|tool synchronization failed|initialize(?: handshake)? failed/i.test(text)) {
89
- return { kind: 'startup', message: 'MCP 初始化或首次工具同步失败,请检查服务配置和 Host 日志' };
95
+ return { kind: 'startup', message: transport === 'stdio'
96
+ ? '本地 MCP 初始化或首次工具同步失败,Host 未提供可识别的具体原因;请检查官方 CLI 状态、启动命令和 Host 日志,不能仅据此判断为授权失败'
97
+ : 'MCP 初始化或首次工具同步失败,请检查服务配置和 Host 日志' };
90
98
  }
91
99
  if (transport === 'stdio') {
92
100
  return { kind: 'startup', message: '本地 MCP Server 启动或初始化失败,请检查命令、参数、运行环境和 Host 日志' };
package/lib/oauth.js CHANGED
@@ -140,18 +140,48 @@ export async function discoverProtectedResource(streamUrl, timeoutMs) {
140
140
 
141
141
  /* ─────────────────────────── 阶段二:OAuth Server Metadata ─────────────────────────── */
142
142
 
143
+ function serverMetadataUrls(issuer, suffix) {
144
+ const url = new URL(issuer);
145
+ if (url.search || url.hash || url.username || url.password) {
146
+ throw new OAuthError('invalid_metadata', 'OAuth issuer must not contain query, fragment or credentials');
147
+ }
148
+ const path = url.pathname.replace(/\/+$/, '');
149
+ // RFC 8414 inserts before the issuer path; retain suffix-form compatibility.
150
+ return [...new Set([
151
+ `${url.origin}/.well-known/${suffix}${path}`,
152
+ `${issuer.replace(/\/+$/, '')}/.well-known/${suffix}`,
153
+ ])];
154
+ }
155
+
156
+ function validateServerMetadata(body, issuer) {
157
+ if (!body || typeof body !== 'object' || Array.isArray(body) || body.issuer !== issuer) {
158
+ throw new OAuthError('invalid_metadata', 'OAuth metadata issuer must exactly match the requested issuer');
159
+ }
160
+ return body;
161
+ }
162
+
163
+ async function fetchServerMetadata(issuer, suffix, timeoutMs) {
164
+ const urls = serverMetadataUrls(issuer, suffix);
165
+ for (let index = 0; index < urls.length; index++) {
166
+ try {
167
+ return validateServerMetadata(await fetchJson(urls[index], { method: 'GET' }, timeoutMs), issuer);
168
+ } catch (error) {
169
+ // Do not mask issuer mismatches, access denial, rate limits or outages.
170
+ if (index === urls.length - 1 || ![404, 405].includes(error.httpStatus)) throw error;
171
+ }
172
+ }
173
+ }
174
+
143
175
  export async function discoverServerMetadata(issuer, timeoutMs) {
144
176
  assertSafeUrl(issuer);
145
- const base = issuer.replace(/\/+$/, '');
146
- const oauthUrl = `${base}/.well-known/oauth-authorization-server`;
147
- const oauthBody = await fetchJson(oauthUrl, { method: 'GET' }, timeoutMs);
177
+ const oauthBody = await fetchServerMetadata(issuer, 'oauth-authorization-server', timeoutMs);
148
178
 
149
179
  // 一些兼容 OIDC 的服务会把 DCR / token revoke 端点只发布在
150
180
  // openid-configuration 中。优先使用 RFC 8414 元数据,仅在扩展端点缺失时合并 OIDC 元数据。
151
181
  let oidcBody = {};
152
182
  if (!oauthBody.registration_endpoint || !oauthBody.revocation_endpoint) {
153
183
  try {
154
- oidcBody = await fetchJson(`${base}/.well-known/openid-configuration`, { method: 'GET' }, timeoutMs);
184
+ oidcBody = await fetchServerMetadata(issuer, 'openid-configuration', timeoutMs);
155
185
  } catch {
156
186
  // OIDC 发现是可选兼容路径;OAuth-only 服务不应因此失败。
157
187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mcp-connector",
3
- "version": "0.2.49",
3
+ "version": "0.2.50",
4
4
  "description": "DeepSeek Harness MCP Connector: connect servers, search tools across active connections, and troubleshoot discovery. Includes a continuously updated catalog; supports OAuth 2.0 PKCE, API keys, Streamable HTTP/stdio, and mcpServers JSON import.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
package/ui/index.html CHANGED
@@ -1331,11 +1331,12 @@
1331
1331
  function guidanceFor(message) {
1332
1332
  const text = String(message || '未知错误');
1333
1333
  const withAdvice = (advice) => `${text.replace(/[。!?.!?\s]+$/, '')}。${advice}`;
1334
+ if (/CLI|OAuth|待授权|管理员|本地 MCP/i.test(text)) return text;
1334
1335
  if (/private-network HTTP requires explicit confirmation/i.test(text)) return withAdvice('如目标是可信的局域网私有 IP,请勾选明文 HTTP 风险确认;请勿对公网或不可信服务开启。');
1335
1336
  if (/protocol not allowed|only https/i.test(text)) return withAdvice('公网地址必须使用 HTTPS;HTTP 只允许回环地址,或经显式确认的私有 IP 字面量。');
1336
1337
  if (/JSON/i.test(text)) return withAdvice('请检查引号、逗号,并确认顶层是 mcpServers 或 connections。');
1337
1338
  if (/401|403|token|凭据|api.?key/i.test(text)) return withAdvice('请核对鉴权方式、Token/API Key 是否有效以及 Header 名。');
1338
- if (/timeout|timed out|fetch|连接|network/i.test(text)) return withAdvice('请检查网络、端点是否可达以及 MCP URL 是否完整。');
1339
+ if (/timeout|timed out|fetch|network|ECONN|ENOTFOUND/i.test(text)) return withAdvice('请检查网络、端点是否可达以及 MCP URL 是否完整。');
1339
1340
  return text;
1340
1341
  }
1341
1342
 
@@ -1418,7 +1419,7 @@
1418
1419
  if (!connector) return;
1419
1420
  openModal(`连接 ${connector.name}`, `
1420
1421
  <input id="scope-connector-id" type="hidden" value="${esc(connectorId)}" />
1421
- <div class="form-note">${connectionLabels(connector).map(esc).join(' · ')}${connector.credentialDescription ? `<p>${esc(connector.credentialDescription)}</p>` : ''}${connectorId === 'dingtalk' ? '<p>尚未登录?请在运行 DSH 的同一系统用户终端执行 <code>npx -y dingtalk-workspace-cli@1.0.61 auth login</code>,完成后点击确认连接。本版本不会自动发起登录;工具注册不代表已获得全部业务权限。</p>' : ''}</div>
1422
+ <div class="form-note">${connectionLabels(connector).map(esc).join(' · ')}${connector.credentialDescription ? `<p>${esc(connector.credentialDescription)}</p>` : ''}${connectorId === 'dingtalk' ? '<p>企业尚未开放 CLI 或您无法取得授权?可以取消,等待管理员处理,无需反复登录或重启。一键授权也不能绕过企业政策。本版本不会自动发起登录;工具注册不代表已获得全部业务权限。</p>' : ''}</div>
1422
1423
  <div class="form-note"><strong>选择连接影响范围</strong>连接配置与凭据仍只保存一份;项目范围会由 Host 在工具可见性和执行层强制隔离。</div>
1423
1424
  ${scopeFieldHtml()}
1424
1425
  <div class="inline-error" id="modal-error" tabindex="-1"></div>