dsh-mcp-connector 0.2.34 → 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,33 @@
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
+
24
+ ## [0.2.35] - 2026-09-05
25
+
26
+ ### Fixed
27
+
28
+ - 市场详情内完成免密连接、凭据配置或 OAuth 重新授权后,现在会原地重新读取连接状态与工具清单,并同步启用 Prompt 和试用按钮;用户在刷新期间主动关闭或切换详情时不会被强制重新打开。
29
+
30
+ ### Verification
31
+
32
+ - 195 项自动测试、lint、版本/营销元数据/商店截图门禁和 npm 发布包白名单/敏感内容扫描全部通过。
33
+
7
34
  ## [0.2.34] - 2026-09-04
8
35
 
9
36
  ### Added
@@ -538,7 +565,9 @@
538
565
  - 外部 URL 与导入 Header 执行安全校验。
539
566
  - iframe 消息校验同源和消息来源。
540
567
 
541
- [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.34...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
570
+ [0.2.35]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.34...v0.2.35
542
571
  [0.2.34]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.33...v0.2.34
543
572
  [0.2.33]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.32...v0.2.33
544
573
  [0.2.32]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.31...v0.2.32
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-02, the public Registry publishes 101 connector descriptors. After merging and deduplicating them with the 4 bundled Qichacha cards, the Marketplace exposes 105 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.34`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.34](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.34).
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-02,公共 Registry 已发布 101 条连接器描述;与随包的 4 张企查查卡片合并去重后,市场页可浏览 105 张卡片,覆盖企业数据、金融投资、法律合规、开发工具、办公协作、调研分析、设计创意、效率工具、其他 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.34`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.34](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.34)。
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.34",
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({
@@ -696,6 +696,18 @@
696
696
  setDetailOpen(false);
697
697
  }
698
698
 
699
+ function isDetailOpenFor(connectorId) {
700
+ return detailConnector?.id === connectorId && $('#detail-overlay').classList.contains('active');
701
+ }
702
+
703
+ async function refreshConnectionUi(connectorId) {
704
+ const reloadOpenDetail = isDetailOpenFor(connectorId);
705
+ const openDetailLoadId = detailLoadId;
706
+ await refresh();
707
+ if (!reloadOpenDetail || detailLoadId !== openDetailLoadId || !isDetailOpenFor(connectorId)) return;
708
+ await window.__mcp.showDetail(connectorId);
709
+ }
710
+
699
711
  function promptVariableNames(text) {
700
712
  const names = [];
701
713
  const seen = new Set();
@@ -1047,13 +1059,21 @@
1047
1059
  function actionHtml(d) {
1048
1060
  const id = esc(d.id);
1049
1061
  const configured = d.connected?.length > 0;
1062
+ const incomplete = configured && d.connectionComplete === false;
1050
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
+ }
1051
1067
  if (configured) {
1052
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>`;
1053
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>`;
1054
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>`;
1055
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>`;
1056
- 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
+ }
1057
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>`;
1058
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>`;
1059
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>`;
@@ -1178,15 +1198,18 @@
1178
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>`
1179
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>`;
1180
1200
  const serverPreview = servers.map((server) => {
1201
+ const modeLabel = server.transport === 'stdio' ? '本地 Agent · 本机文件' : '远程 Agent · 在线链接文档';
1181
1202
  const endpoint = server.transport === 'stdio'
1182
1203
  ? `stdio · ${server.command || ''} ${(server.args || []).join(' ')}`.trim()
1183
1204
  : server.url;
1184
- return `<div>${esc(server.serverName)} · ${esc(endpoint)}</div>`;
1205
+ return `<div><strong>${esc(modeLabel)}</strong><br>${esc(server.serverName)} · ${esc(endpoint)}</div>`;
1185
1206
  }).join('');
1207
+ const mixedDocumentModes = usesStdio && servers.some((server) => server.transport !== 'stdio');
1186
1208
  openModal(`配置 ${preset.name}`, `
1187
1209
  <input id="cfg-connector-id" type="hidden" value="${esc(preset.id)}" />
1188
1210
  <input id="cfg-auth" type="hidden" value="${esc(authMode)}" />
1189
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>' : ''}
1190
1213
  <div class="server-preview">${serverPreview}</div>
1191
1214
  ${scopeFieldHtml()}
1192
1215
  ${credentialFields}
@@ -1337,7 +1360,7 @@
1337
1360
  markCommunityCtaEligible();
1338
1361
  closeModal();
1339
1362
  await call('healthCheck', { connectorId });
1340
- await refresh();
1363
+ await refreshConnectionUi(connectorId);
1341
1364
  }
1342
1365
  },
1343
1366
  confirmConnect() {
@@ -1484,11 +1507,9 @@
1484
1507
  });
1485
1508
  setModalBusy(false);
1486
1509
  if (!r.ok) { showModalError(r.message); return; }
1487
- const configuredFromDetail = detailConnector?.id === connectorId && $('#detail-overlay').classList.contains('active');
1488
1510
  closeModal();
1489
- if (configuredFromDetail) closeDetail();
1490
1511
  markCommunityCtaEligible();
1491
- showToast(r.message, true); await refresh();
1512
+ showToast(r.message, true); await refreshConnectionUi(connectorId);
1492
1513
  return;
1493
1514
  }
1494
1515
  const name = $('#cfg-name').value.trim();
@@ -1581,14 +1602,17 @@
1581
1602
  const d = marketItems[connectorId];
1582
1603
  if (!d) return;
1583
1604
  detailConnector = d;
1584
- detailFailureState = d.connectionState || '';
1585
- 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);
1586
1608
  const loadId = ++detailLoadId;
1587
1609
  $('#detail-icon').innerHTML = isImageIcon(d.icon)
1588
1610
  ? `<img src="${esc(d.icon)}" alt="" onerror="this.parentElement.innerHTML='🔌';" />`
1589
1611
  : `<span style="font-size:24px;">${esc(d.icon || '')}</span>`;
1590
1612
  $('#detail-name').textContent = d.name || '';
1591
- const initialStateLabel = d.connectionLabel || (d.connected?.length ? '已配置' : '未连接');
1613
+ const initialStateLabel = incomplete
1614
+ ? `待补充 ${d.missingServers?.length || 1} 个服务`
1615
+ : (d.connectionLabel || (d.connected?.length ? '已配置' : '未连接'));
1592
1616
  $('#detail-vendor').textContent = d.vendor ? `${d.vendor} · ${initialStateLabel} ${d.connected?.length || 0} 个服务` : `${initialStateLabel} ${d.connected?.length || 0} 个服务`;
1593
1617
  $('#detail-desc').textContent = d.description || d.summary || '';
1594
1618
  detailPrompts = (d.prompts || []).filter((prompt) => prompt?.text || prompt?.title);
@@ -1606,7 +1630,7 @@
1606
1630
  $('#detail-disconnect-btn').style.display = d.connected?.length > 0 ? 'block' : 'none';
1607
1631
  const needsCredential = ['bearer', 'api-key'].includes(d.authMode);
1608
1632
  $('#detail-try-btn').textContent = detailNeedsReconfigure
1609
- ? 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') : '🔄 重新检查'
1610
1634
  : d.connected?.length > 0 ? t('try') : needsCredential ? t('configureConnect') : t('connectTry');
1611
1635
  if (!d.connected?.length && !d.toolsSnapshot?.length) {
1612
1636
  $('#tools-summary').textContent = '连接后查看工具';
@@ -1647,10 +1671,13 @@
1647
1671
  renderPromptPage();
1648
1672
  return;
1649
1673
  }
1650
- detailFailureState = toolsResult.detail?.connectionState || 'healthy';
1651
- detailNeedsReconfigure = false;
1652
- $('#detail-vendor').textContent = d.vendor ? `${d.vendor} · ${detailFailureState === 'degraded' ? '部分服务可用' : '已连接'} ${d.connected?.length || 0} 个服务` : `${detailFailureState === 'degraded' ? '部分服务可用' : '已连接'} ${d.connected?.length || 0} 个服务`;
1653
- $('#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');
1654
1681
  renderPromptPage();
1655
1682
  detailToolServers = toolsResult.detail?.servers || [];
1656
1683
  updateToolsSummary();
@@ -1738,6 +1765,10 @@
1738
1765
  });
1739
1766
  $('#detail-try-btn').addEventListener('click', async () => {
1740
1767
  if (detailNeedsReconfigure) {
1768
+ if (detailFailureState === 'incomplete' && ['bearer', 'api-key'].includes(detailConnector?.authMode)) {
1769
+ openAddConnection('manual', detailConnector);
1770
+ return;
1771
+ }
1741
1772
  if (detailFailureState === 'reauth' && ['bearer', 'api-key'].includes(detailConnector?.authMode)) {
1742
1773
  openAddConnection('manual', detailConnector);
1743
1774
  return;
@@ -1749,8 +1780,7 @@
1749
1780
  if (r.ok) {
1750
1781
  markCommunityCtaEligible();
1751
1782
  await call('healthCheck', { connectorId: id });
1752
- closeDetail();
1753
- await refresh();
1783
+ await refreshConnectionUi(id);
1754
1784
  }
1755
1785
  return;
1756
1786
  }