dsh-mcp-connector 0.2.35 → 0.2.36

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,23 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.36] - 2026-09-06
8
+
9
+ ### Changed
10
+
11
+ - “企查查·文档报告”更名为“企查查·智能文档解析”,产品说明、搜索标签、官网入口和示例 Prompt 调整为 OCR、文档结构化解析及 Markdown 输出场景。
12
+ - 智能文档解析卡片改为一次录入完整 Authorization,同时配置远程在线链接 `qcc-document` 和本机文件 `qcc-document-mcp` 两个入口。
13
+
14
+ ### Fixed
15
+
16
+ - 市场目录会比较已安装连接与卡片声明的 Server;旧版本仅安装远程入口时显示“补充本地解析”,凭据失效时显示“重新配置凭据”,不再误报完整连接或提示 OAuth 重新授权。
17
+ - 配置页明确区分智能文档解析 API Key 与旧版企查查 OAuth 登录令牌,避免本地上传网关返回 `200215` 身份认证失败。
18
+
19
+ ### Verification
20
+
21
+ - 196 项自动测试、lint、版本/营销元数据/商店截图门禁和 npm 发布包白名单/敏感内容扫描全部通过。
22
+ - 远端 `qcc-document` 已用公开 PDF 完成真实解析;本地 `qcc-document-mcp` 已完成 stdio 启动和工具注册验证,并确认旧 OAuth Token 会被上传网关按预期拒绝,必须改用智能文档解析 API Key。
23
+
7
24
  ## [0.2.35] - 2026-09-05
8
25
 
9
26
  ### Fixed
@@ -548,7 +565,8 @@
548
565
  - 外部 URL 与导入 Header 执行安全校验。
549
566
  - iframe 消息校验同源和消息来源。
550
567
 
551
- [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.35...HEAD
568
+ [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.36...HEAD
569
+ [0.2.36]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.35...v0.2.36
552
570
  [0.2.35]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.34...v0.2.35
553
571
  [0.2.34]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.33...v0.2.34
554
572
  [0.2.33]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.32...v0.2.33
package/README.en.md CHANGED
@@ -66,7 +66,7 @@ If the plugin helps you connect an MCP server faster, consider [starring the rep
66
66
  - Explicit, non-destructive migration from the two earlier Qichacha OAuth plugins, plus active-plugin conflict detection that blocks duplicate server management and credential overwrites.
67
67
 
68
68
  <!-- catalog-stats:start -->
69
- As of 2026-09-04, the public Registry publishes 100 connector descriptors. After merging and deduplicating them with the 4 bundled Qichacha cards, the Marketplace exposes 104 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.
69
+ As of 2026-09-05, the public Registry publishes 105 connector descriptors. After merging and deduplicating them with the 4 bundled Qichacha cards, the Marketplace exposes 109 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.
70
70
  <!-- catalog-stats:end -->
71
71
 
72
72
  ## Interface and demo
@@ -171,7 +171,7 @@ npm run dev:ui
171
171
 
172
172
  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.
173
173
 
174
- The current public version is [`dsh-mcp-connector@0.2.35`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.35](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.35).
174
+ The current public version is [`dsh-mcp-connector@0.2.36`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.36](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.36).
175
175
 
176
176
  See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
177
177
 
package/README.md CHANGED
@@ -68,7 +68,7 @@ dsh plugin --profile web add dsh-mcp-connector
68
68
  - 对话工具:`mcp_connector_catalog`、`connect`、`configure`、`import_json`、`export_config`、`snapshot`、`install_from_url`、`status`、`scope`、`health_check`、`policy`、`set_enabled`、`disconnect`、`refresh_catalog`、`publish`、`tools_list`。
69
69
 
70
70
  <!-- catalog-stats:start -->
71
- 截至 2026-09-04,公共 Registry 已发布 100 条连接器描述;与随包的 4 张企查查卡片合并去重后,市场页可浏览 104 张卡片,覆盖企业数据、金融投资、法律合规、开发工具、办公协作、调研分析、设计创意、效率工具、其他 9 类。推荐位严格保留 4 张企查查卡片、北大法宝和 Wind,共 6 张;其他连接器按业务分类展示。Registry 可独立持续更新,实际数量以客户端刷新后的市场页签徽标和上方实时统计徽标为准。
71
+ 截至 2026-09-05,公共 Registry 已发布 105 条连接器描述;与随包的 4 张企查查卡片合并去重后,市场页可浏览 109 张卡片,覆盖企业数据、金融投资、法律合规、开发工具、办公协作、调研分析、设计创意、效率工具、其他 9 类。推荐位严格保留 4 张企查查卡片、北大法宝和 Wind,共 6 张;其他连接器按业务分类展示。Registry 可独立持续更新,实际数量以客户端刷新后的市场页签徽标和上方实时统计徽标为准。
72
72
  <!-- catalog-stats:end -->
73
73
 
74
74
  ## 界面与演示
@@ -167,7 +167,7 @@ npm run dev:ui
167
167
 
168
168
  公共 Registry 每次合并后会生成 `catalog-stats.json`;本仓库的定时工作流每小时同步中英文介绍和统计快照。npm 页面中的静态正文随版本发布更新,上方动态统计徽标则直接读取 Registry,可在不发布新 npm 版本时保持实时数量一致。
169
169
 
170
- 当前公开版本为 [`dsh-mcp-connector@0.2.35`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.35](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.35)。
170
+ 当前公开版本为 [`dsh-mcp-connector@0.2.36`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.36](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.36)。
171
171
 
172
172
  版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
173
173
  Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
@@ -131,34 +131,50 @@
131
131
  },
132
132
  {
133
133
  "id": "qcc-document",
134
- "name": "企查查·文档报告",
134
+ "name": "企查查·智能文档解析",
135
135
  "vendor": "企查查 Qichacha",
136
136
  "icon": "/mcp-connector/ui/assets/qcc-logo.svg",
137
137
  "category": "效率工具",
138
- "summary": "企业报告生成 / 文档分析",
139
- "description": "基于企业数据自动生成分析报告、尽职调查文档,支持文档结构化解析与内容提取。",
140
- "tags": ["文档", "报告", "分析"],
138
+ "summary": "本机文件 / 在线链接 · OCR 与结构化解析",
139
+ "description": "智能解析 PDF、图片等文档:远程入口处理公网可访问的在线链接,本地入口读取对话中上传、粘贴或指定路径的本机文件并自动提交解析,输出 Markdown 与结构化内容。本地入口需 Node.js 20+。",
140
+ "tags": ["智能文档解析", "OCR", "本机文件", "在线文档", "PDF", "图片", "Markdown"],
141
141
  "published": true,
142
142
  "featured": true,
143
- "homepage": "https://open.qcc.com",
144
- "promptVariables": [
145
- { "name": "company", "label": "企业名称", "default": "小米科技有限责任公司", "placeholder": "例如:小米科技有限责任公司", "required": true }
146
- ],
143
+ "homepage": "https://agent.qcc.com/doc",
147
144
  "auth": {
148
- "mode": "oauth2-pkce",
149
- "issuer": "https://agent.qcc.com",
150
- "scope": "mcp:tools",
151
- "clientName": "DeepSeek Harness - MCP 连接器",
152
- "tokenEndpointAuthMethod": "none",
153
- "grantSharing": "issuer"
145
+ "mode": "api-key",
146
+ "apiKeyHeader": "Authorization",
147
+ "credentialName": "智能文档解析 API Key Authorization",
148
+ "credentialPlaceholder": "Bearer YOUR_API_KEY",
149
+ "credentialDescription": "请输入智能文档解析产品签发的完整 Authorization 值(Bearer + 空格 + API Key),不是企查查 OAuth 登录令牌。一次配置后,远程在线链接与本地文件解析共用此凭据。",
150
+ "credentialHelpLabel": "前往企查查智能文档解析获取 API Key",
151
+ "credentialFields": [
152
+ {
153
+ "key": "authorization",
154
+ "label": "Authorization",
155
+ "placeholder": "Bearer YOUR_API_KEY",
156
+ "description": "格式为 Bearer + 空格 + 智能文档解析 API Key;OAuth 登录令牌不能用于本地文件上传。凭据仅保存在 DSH 本机。",
157
+ "helpLabel": "前往企查查智能文档解析获取 API Key",
158
+ "required": true,
159
+ "secret": true
160
+ }
161
+ ]
154
162
  },
155
163
  "servers": [
156
- { "serverKey": "document", "url": "https://agent.qcc.com/mcp/document/stream", "serverName": "qcc-document", "transport": "streamable-http", "headers": {} }
164
+ { "serverKey": "document", "url": "https://agent.qcc.com/mcp/document/stream", "serverName": "qcc-document", "transport": "streamable-http", "headers": {} },
165
+ {
166
+ "serverKey": "document-local",
167
+ "serverName": "qcc-document-mcp",
168
+ "transport": "stdio",
169
+ "command": "npx",
170
+ "args": ["-y", "qcc-document-mcp"],
171
+ "credentialBindings": { "QCC_DOCUMENT_AUTHORIZATION": "authorization" }
172
+ }
157
173
  ],
158
174
  "prompts": [
159
- { "title": "企业尽调报告", "text": " {{company}} 生成一份企业尽职调查报告", "server": "document" },
160
- { "title": "企业风险报告", "text": " {{company}} 生成一份企业风险分析报告", "server": "document" },
161
- { "title": "工商信息摘要", "text": "整理 {{company}} 的工商与股权信息摘要", "server": "document" }
175
+ { "title": "解析在线文档", "text": "解析这份在线文档:请把公网可访问的 PDF 或图片 URL 粘贴到这里,并输出 Markdown 与关键字段。", "server": "document" },
176
+ { "title": "解析上传文件", "text": "解析我在对话中上传或粘贴的这份 PDF 或图片,提取正文、表格和关键字段。", "server": "document-local" },
177
+ { "title": "解析本机文件", "text": "解析本机文件:请把文件路径替换为 ~/Desktop/example.pdf,并输出 Markdown 与结构化摘要。", "server": "document-local" }
162
178
  ]
163
179
  },
164
180
  {
@@ -75,6 +75,15 @@ Bearer/API Key 连接器会先执行 MCP `initialize` 验证。所有 Server 通
75
75
 
76
76
  stdio 市场卡片也可能显示一个或多个凭据字段,例如 API Token、区域或租户标识。卡片目录只声明字段名称及其环境变量映射;提交后,真实值仅保存到 DSH 本机连接记录,并由插件注入该 stdio 进程的 `env`。市场、状态页和日志不会返回这些值。插件会等待 Host 完成首次 MCP 初始化与工具同步后再保存连接;失败或超时不会增加已安装数量,卡片也不会提前显示“已连接”。
77
77
 
78
+ “企查查·智能文档解析”会一次配置两个互不冲突的入口,并共用同一份完整 `Authorization` 值(格式为 `Bearer YOUR_API_KEY`):
79
+
80
+ - `qcc-document`:远程 Agent,解析公网可访问的在线文档或图片 URL。
81
+ - `qcc-document-mcp`:本地 Agent,通过 `npx -y qcc-document-mcp` 读取对话中上传、粘贴或指定路径的本机文件,自动上传并提交解析;要求 Node.js 20+。
82
+
83
+ 这里必须使用智能文档解析产品签发的 API Key,不是企查查 OAuth 登录令牌。旧版远程 `qcc-document` 的 OAuth 授权可以继续访问远程 MCP,但不能直接充当 `QCC_DOCUMENT_AUTHORIZATION`;本地上传网关会拒绝这种令牌。请从卡片的“前往企查查智能文档解析获取 API Key”入口取得凭据,且不要把真实值粘贴到对话、日志或截图中。
84
+
85
+ 旧版本只配置过远程入口时,卡片会显示“补充本地解析”,不会把单入口误报为完整连接。重新录入一次 Authorization 后,插件先验证两个 Server,再原子保存;任一入口失败都不会留下半套配置。
86
+
78
87
  OAuth 一键连接要求服务商支持标准 OAuth 2.1/PKCE 和公开元数据发现。动态客户端注册既支持无需客户端密钥的 `none`,也支持服务商签发密钥的 `client_secret_post` 与 `client_secret_basic`。客户端密钥仅与 OAuth Grant 一同保存在 DSH 本机,用于换取、刷新和撤销 Token;插件不会要求用户把 OAuth Token 或客户端密钥复制到聊天中。如服务商在动态客户端注册阶段返回 HTTP 403,页面会明确提示“客户端注册被拒绝、尚未进入浏览器授权”;这通常需要服务商审核或登记客户端,重试用户授权无法绕过。
79
88
 
80
89
  当市场描述声明 `grantSharing: "issuer"` 时,同一账号下相同 issuer、scope 和客户端鉴权方式的卡片共享一组 Grant。首次授权仍只启用用户点击的卡片;之后连接同组卡片会直接复用现有授权,不再重复打开 OAuth 页面。升级时会验证并自动归并旧版本留下的同 issuer 多份 Grant;Desktop 与 `dsh web` 并发时,插件使用跨进程锁串行轮换,并从每 Grant 独立原子日志读取最新 Token,避免 DSH 整文件 storage 的旧进程快照覆盖新凭据。网络、OAuth 元数据发现或服务端 5xx 等暂时故障只进入“自动重试中”;确认 journal 中也没有更新 Token,且明确收到 Refresh Token/客户端失效错误时,才进入“需重新授权”。
package/lib/index.js CHANGED
@@ -1709,6 +1709,13 @@ export async function apply(ctx, config) {
1709
1709
  const visibleRecords = workspaceId
1710
1710
  ? connectorRecords.filter((record) => connectionVisibleInWorkspace(connectionScopes.binding(record.key), workspaceId))
1711
1711
  : connectorRecords;
1712
+ const missingServers = (d.servers ?? []).filter((server) => !visibleRecords.some((record) => (
1713
+ record.serverKey === server.serverKey || record.serverName === server.serverName
1714
+ ))).map((server) => ({
1715
+ serverKey: server.serverKey,
1716
+ serverName: server.serverName,
1717
+ transport: server.transport,
1718
+ }));
1712
1719
  return ({
1713
1720
  id: d.id,
1714
1721
  name: d.name,
@@ -1734,6 +1741,8 @@ export async function apply(ctx, config) {
1734
1741
  probeReportUrl: d.probeReportUrl,
1735
1742
  toolsSnapshot: d.toolsSnapshot ?? [],
1736
1743
  connected: visibleRecords.map((record) => record.key),
1744
+ connectionComplete: missingServers.length === 0,
1745
+ missingServers,
1737
1746
  connectedScopes: connectorRecords.map((record) => ({ key: record.key, ...scopeDetail(record, workspaceId) })),
1738
1747
  connectionState: health.connectionState,
1739
1748
  connectionLabel: health.label,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mcp-connector",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
4
4
  "description": "DeepSeek Harness MCP Connector and MCP Server marketplace with over one hundred MCP connectors, continuously updated. Discover, authorize, and manage connections in one place; supports OAuth 2.0 PKCE, API keys, stdio/HTTP, mcpServers JSON import, and tool and prompt discovery. Maintained by Qichacha/QCC.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
package/ui/index.html CHANGED
@@ -521,7 +521,7 @@
521
521
  reauthorize: '需重新授权', recovering: '自动重试中', connectionError: '连接异常', partialError: '部分异常', disabled: '已停用',
522
522
  send: '发送', fillSend: '填写并发送', connectToUse: '连接后使用', try: '💬 去试试',
523
523
  connectTry: '🔗 连接后试用', configureConnect: '🔑 录入凭据并连接',
524
- reconfigureCredential: '🔑 重新配置凭据', loading: '加载中…',
524
+ reconfigureCredential: '🔑 重新配置凭据', completeSetup: '补充本地解析', loading: '加载中…',
525
525
  };
526
526
  const t = (key) => LABELS[key] || key;
527
527
  const MCP_JSON_EXAMPLE = JSON.stringify({
@@ -1059,13 +1059,21 @@
1059
1059
  function actionHtml(d) {
1060
1060
  const id = esc(d.id);
1061
1061
  const configured = d.connected?.length > 0;
1062
+ const incomplete = configured && d.connectionComplete === false;
1062
1063
  const state = d.connectionState || (configured ? 'configured' : 'disconnected');
1064
+ if (incomplete && ['bearer', 'api-key'].includes(d.authMode)) {
1065
+ return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.openConfig('${id}')">${t('completeSetup')}</button>`;
1066
+ }
1063
1067
  if (configured) {
1064
1068
  if (state === 'healthy') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn ghost" disabled>${t('connected')}</button>`;
1065
1069
  if (state === 'unknown') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('unknown')}</button>`;
1066
1070
  if (state === 'configured') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn ghost" disabled>${t('configured')}</button>`;
1067
1071
  if (state === 'disabled') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn ghost" disabled>${t('disabled')}</button>`;
1068
- if (state === 'reauth') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('reauthorize')}</button>`;
1072
+ if (state === 'reauth') {
1073
+ const credentialAuth = ['bearer', 'api-key'].includes(d.authMode);
1074
+ const action = credentialAuth ? `window.__mcp.openConfig('${id}')` : `window.__mcp.showDetail('${id}')`;
1075
+ return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();${action}">${credentialAuth ? t('reconfigureCredential') : t('reauthorize')}</button>`;
1076
+ }
1069
1077
  if (state === 'recovering') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('recovering')}</button>`;
1070
1078
  if (state === 'degraded') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('partialError')}</button>`;
1071
1079
  if (state === 'unavailable') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('details')}</button><button class="btn status-bad" type="button" onclick="event.stopPropagation();window.__mcp.showDetail('${id}')">${t('connectionError')}</button>`;
@@ -1190,15 +1198,18 @@
1190
1198
  ? `<label for="cfg-token">${esc(credentialName)}(必填)</label><input id="cfg-token" type="password" autocomplete="off" placeholder="${esc(credentialPlaceholder)}" /><div class="field-help">${esc(credentialDescription)}</div>`
1191
1199
  : `<div class="form-row"><div><label for="cfg-key-header">API Key Header</label><input id="cfg-key-header" value="${esc(preset.apiKeyHeader || 'X-Api-Key')}" /></div><div><label for="cfg-key-value">${esc(credentialName)}(必填)</label><input id="cfg-key-value" type="password" autocomplete="off" placeholder="${esc(credentialPlaceholder)}" /></div></div><div class="field-help">${esc(credentialDescription)}</div>`;
1192
1200
  const serverPreview = servers.map((server) => {
1201
+ const modeLabel = server.transport === 'stdio' ? '本地 Agent · 本机文件' : '远程 Agent · 在线链接文档';
1193
1202
  const endpoint = server.transport === 'stdio'
1194
1203
  ? `stdio · ${server.command || ''} ${(server.args || []).join(' ')}`.trim()
1195
1204
  : server.url;
1196
- return `<div>${esc(server.serverName)} · ${esc(endpoint)}</div>`;
1205
+ return `<div><strong>${esc(modeLabel)}</strong><br>${esc(server.serverName)} · ${esc(endpoint)}</div>`;
1197
1206
  }).join('');
1207
+ const mixedDocumentModes = usesStdio && servers.some((server) => server.transport !== 'stdio');
1198
1208
  openModal(`配置 ${preset.name}`, `
1199
1209
  <input id="cfg-connector-id" type="hidden" value="${esc(preset.id)}" />
1200
1210
  <input id="cfg-auth" type="hidden" value="${esc(authMode)}" />
1201
1211
  <div class="form-note"><strong>一次配置 ${servers.length} 个 MCP Server</strong>以下 Server 共用同一份 ${esc(credentialName)};凭据仅保存在本机,不会写入市场目录。</div>
1212
+ ${mixedDocumentModes ? '<div class="form-note"><strong>在线链接 + 本机文件同时启用</strong>远程入口解析公网可访问的 URL;本地入口读取对话中上传、粘贴或指定路径的文件并自动提交解析,需 Node.js 20+。</div>' : ''}
1202
1213
  <div class="server-preview">${serverPreview}</div>
1203
1214
  ${scopeFieldHtml()}
1204
1215
  ${credentialFields}
@@ -1591,14 +1602,17 @@
1591
1602
  const d = marketItems[connectorId];
1592
1603
  if (!d) return;
1593
1604
  detailConnector = d;
1594
- detailFailureState = d.connectionState || '';
1595
- detailNeedsReconfigure = ['reauth', 'recovering', 'unavailable'].includes(detailFailureState);
1605
+ const incomplete = d.connected?.length > 0 && d.connectionComplete === false;
1606
+ detailFailureState = incomplete ? 'incomplete' : (d.connectionState || '');
1607
+ detailNeedsReconfigure = incomplete || ['reauth', 'recovering', 'unavailable'].includes(detailFailureState);
1596
1608
  const loadId = ++detailLoadId;
1597
1609
  $('#detail-icon').innerHTML = isImageIcon(d.icon)
1598
1610
  ? `<img src="${esc(d.icon)}" alt="" onerror="this.parentElement.innerHTML='🔌';" />`
1599
1611
  : `<span style="font-size:24px;">${esc(d.icon || '')}</span>`;
1600
1612
  $('#detail-name').textContent = d.name || '';
1601
- const initialStateLabel = d.connectionLabel || (d.connected?.length ? '已配置' : '未连接');
1613
+ const initialStateLabel = incomplete
1614
+ ? `待补充 ${d.missingServers?.length || 1} 个服务`
1615
+ : (d.connectionLabel || (d.connected?.length ? '已配置' : '未连接'));
1602
1616
  $('#detail-vendor').textContent = d.vendor ? `${d.vendor} · ${initialStateLabel} ${d.connected?.length || 0} 个服务` : `${initialStateLabel} ${d.connected?.length || 0} 个服务`;
1603
1617
  $('#detail-desc').textContent = d.description || d.summary || '';
1604
1618
  detailPrompts = (d.prompts || []).filter((prompt) => prompt?.text || prompt?.title);
@@ -1616,7 +1630,7 @@
1616
1630
  $('#detail-disconnect-btn').style.display = d.connected?.length > 0 ? 'block' : 'none';
1617
1631
  const needsCredential = ['bearer', 'api-key'].includes(d.authMode);
1618
1632
  $('#detail-try-btn').textContent = detailNeedsReconfigure
1619
- ? d.authMode === 'oauth2-pkce' && detailFailureState === 'reauth' ? '🔐 重新授权' : needsCredential && detailFailureState === 'reauth' ? t('reconfigureCredential') : '🔄 重新检查'
1633
+ ? detailFailureState === 'incomplete' ? t('completeSetup') : d.authMode === 'oauth2-pkce' && detailFailureState === 'reauth' ? '🔐 重新授权' : needsCredential && detailFailureState === 'reauth' ? t('reconfigureCredential') : '🔄 重新检查'
1620
1634
  : d.connected?.length > 0 ? t('try') : needsCredential ? t('configureConnect') : t('connectTry');
1621
1635
  if (!d.connected?.length && !d.toolsSnapshot?.length) {
1622
1636
  $('#tools-summary').textContent = '连接后查看工具';
@@ -1657,10 +1671,13 @@
1657
1671
  renderPromptPage();
1658
1672
  return;
1659
1673
  }
1660
- detailFailureState = toolsResult.detail?.connectionState || 'healthy';
1661
- detailNeedsReconfigure = false;
1662
- $('#detail-vendor').textContent = d.vendor ? `${d.vendor} · ${detailFailureState === 'degraded' ? '部分服务可用' : '已连接'} ${d.connected?.length || 0} 个服务` : `${detailFailureState === 'degraded' ? '部分服务可用' : '已连接'} ${d.connected?.length || 0} 个服务`;
1663
- $('#detail-try-btn').textContent = t('try');
1674
+ detailFailureState = incomplete ? 'incomplete' : (toolsResult.detail?.connectionState || 'healthy');
1675
+ detailNeedsReconfigure = incomplete;
1676
+ const connectedLabel = incomplete
1677
+ ? `待补充 ${d.missingServers?.length || 1} 个服务`
1678
+ : `${detailFailureState === 'degraded' ? '部分服务可用' : '已连接'} ${d.connected?.length || 0} 个服务`;
1679
+ $('#detail-vendor').textContent = d.vendor ? `${d.vendor} · ${connectedLabel}` : connectedLabel;
1680
+ $('#detail-try-btn').textContent = incomplete ? t('completeSetup') : t('try');
1664
1681
  renderPromptPage();
1665
1682
  detailToolServers = toolsResult.detail?.servers || [];
1666
1683
  updateToolsSummary();
@@ -1748,6 +1765,10 @@
1748
1765
  });
1749
1766
  $('#detail-try-btn').addEventListener('click', async () => {
1750
1767
  if (detailNeedsReconfigure) {
1768
+ if (detailFailureState === 'incomplete' && ['bearer', 'api-key'].includes(detailConnector?.authMode)) {
1769
+ openAddConnection('manual', detailConnector);
1770
+ return;
1771
+ }
1751
1772
  if (detailFailureState === 'reauth' && ['bearer', 'api-key'].includes(detailConnector?.authMode)) {
1752
1773
  openAddConnection('manual', detailConnector);
1753
1774
  return;