dsh-mcp-connector 0.2.39 → 0.2.40

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,21 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.40] - 2026-09-09
8
+
9
+ ### Added
10
+
11
+ - “已安装”页为用户自定义和 JSON 导入的 MCP 连接新增“重命名”,启用状态下也可无中断修改显示名。
12
+ - 新增 `mcp_connector_rename` 对话工具,便于按连接 key 修改显示名。
13
+
14
+ ### Changed
15
+
16
+ - 重命名只更新本机连接元数据并创建可恢复快照;connection key、`serverName`、工具前缀、连接范围、凭据和 Host 挂载均保持不变。
17
+
18
+ ### Verification
19
+
20
+ - 209 项自动测试、lint、版本/营销元数据/商店截图门禁和 npm 发布包白名单/敏感内容扫描全部通过;新增覆盖显示名校验、Web API、对话工具、界面入口、启用状态下无重挂载、脱敏导出、快照与重启持久化。
21
+
7
22
  ## [0.2.39] - 2026-09-08
8
23
 
9
24
  ### Fixed
@@ -607,7 +622,8 @@
607
622
  - 外部 URL 与导入 Header 执行安全校验。
608
623
  - iframe 消息校验同源和消息来源。
609
624
 
610
- [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.39...HEAD
625
+ [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.40...HEAD
626
+ [0.2.40]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.39...v0.2.40
611
627
  [0.2.39]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.38...v0.2.39
612
628
  [0.2.38]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.37...v0.2.38
613
629
  [0.2.37]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.36...v0.2.37
package/README.en.md CHANGED
@@ -175,7 +175,7 @@ npm run dev:ui
175
175
 
176
176
  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.
177
177
 
178
- The current public version is [`dsh-mcp-connector@0.2.39`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.39](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.39).
178
+ The current public version is [`dsh-mcp-connector@0.2.40`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.40](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.40).
179
179
 
180
180
  See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
181
181
 
package/README.md CHANGED
@@ -171,7 +171,7 @@ npm run dev:ui
171
171
 
172
172
  公共 Registry 每次合并后会生成 `catalog-stats.json`;本仓库的定时工作流每小时同步中英文介绍和统计快照。npm 页面中的静态正文随版本发布更新,上方动态统计徽标则直接读取 Registry,可在不发布新 npm 版本时保持实时数量一致。
173
173
 
174
- 当前公开版本为 [`dsh-mcp-connector@0.2.39`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.39](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.39)。
174
+ 当前公开版本为 [`dsh-mcp-connector@0.2.40`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.40](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.40)。
175
175
 
176
176
  版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
177
177
  Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
@@ -152,6 +152,7 @@ stdio 进程由 `@deepseek-ai/dsh-mcp-client` 管理,插件透传 `command`、
152
152
  在“已安装”中可以:
153
153
 
154
154
  - 查看连接状态和 Server 数量;
155
+ - 修改自定义或 JSON 导入连接的显示名,无需先停用连接;
155
156
  - 启用或停用连接;
156
157
  - 重新授权 OAuth,或重新录入 Token/API Key;
157
158
  - 执行健康检查;
@@ -159,12 +160,14 @@ stdio 进程由 `@deepseek-ai/dsh-mcp-client` 管理,插件透传 `command`、
159
160
 
160
161
  OAuth 断开时,插件会尽力调用服务商的撤销端点;无撤销端点时仍会删除 DSH 本机授权记录。连接状态发生变化后,可点击“刷新连接器目录”重新检查。
161
162
 
163
+ “重命名”只修改用户看到的连接显示名,不会改变 connection key、`serverName`、`mcp__<serverName>__*` 工具前缀、连接范围、凭据或启停状态,也不会重新挂载 MCP Server。市场连接器的名称继续由公共目录维护。
164
+
162
165
  ### 7.1 配置备份与恢复
163
166
 
164
167
  “已安装”页的“配置备份”提供两种互补能力:
165
168
 
166
169
  - **脱敏导出**:复制或下载可携带 JSON。Token/API Key、静态 Header/env 值、stdio 参数、本地目录以及可能含凭据的 URL 会变成明确占位符;目标设备导入前必须重填。OAuth 只导出连接引用,必须从市场重新授权。
167
- - **本机快照**:连接、配置、导入、启停或断开前自动保存变更范围,也可手动创建;最多保留 20 个。恢复前可查看将恢复和移除的 key,恢复过程中任一 Server 或持久化步骤失败会整体回到恢复前状态。
170
+ - **本机快照**:连接、配置、导入、重命名、启停或断开前自动保存变更范围,也可手动创建;最多保留 20 个。恢复前可查看将恢复和移除的 key,恢复过程中任一 Server 或持久化步骤失败会整体回到恢复前状态。
168
171
 
169
172
  快照可能包含用于原样恢复的本机凭据,因此只保存在当前 profile 的 storage domain,不会通过 Web API、对话工具或日志原样返回。断开并撤销 OAuth 后,快照不能重建服务端 Grant,会明确要求重新授权。完整边界见[配置备份说明](CONFIG-BACKUP.md)。
170
173
 
package/lib/index.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * - 连接记录与 OAuth grant 持久化,重启恢复与跨 Host Token 轮换
8
8
  * - 通过 ctx.loader 动态配置 @deepseek-ai/dsh-mcp-client 条目
9
9
  * - 对话工具:mcp_connector_catalog / connect / configure / import_json /
10
- * export_config / snapshots / install_from_url / status / set_enabled /
10
+ * export_config / snapshots / install_from_url / status / rename / set_enabled /
11
11
  * disconnect / refresh_catalog / publish
12
12
  */
13
13
  import { randomUUID } from 'node:crypto';
@@ -22,6 +22,8 @@ import {
22
22
  DEFAULT_REFRESH_RETRY_BASE_MS,
23
23
  DEFAULT_REFRESH_RETRY_MAX_MS,
24
24
  DEFAULT_CATALOG_TTL_MS,
25
+ CUSTOM_CONNECTOR_ID,
26
+ JSON_CONNECTOR_ID,
25
27
  } from './constants.js';
26
28
  import { defineConnectorDomain, ConnectionStore, GrantStore, CatalogStore, SnapshotStore, GovernanceStore, ConnectionScopeStore } from './stores.js';
27
29
  import { normalizeConnectorDescriptor, normalizeConnectionRecord } from './schema.js';
@@ -36,7 +38,7 @@ import { listMcpTools, McpHttpError } from './mcp-http.js';
36
38
  import { registerTools } from './tools.js';
37
39
  import { mountWebRoutes } from './web.js';
38
40
  import { createVersionStatusService } from './version-status.js';
39
- import { assertSafeUrl } from './util.js';
41
+ import { assertSafeUrl, normalizeConnectionDisplayName } from './util.js';
40
42
  import { readLegacyGrantCandidates, planLegacyMigration, toConnectorGrant, toConnectionRecords } from './migration.js';
41
43
  import { classifyRefreshFailure, refreshRetryDelay } from './grant-lifecycle.js';
42
44
  import { buildHealthSummary, diagnosticForRecord } from './diagnostics.js';
@@ -575,6 +577,10 @@ export async function apply(ctx, config) {
575
577
  if (record?.connectorId) state.health.delete(record.connectorId);
576
578
  }
577
579
 
580
+ function connectionDisplayNameEditable(record) {
581
+ return [CUSTOM_CONNECTOR_ID, JSON_CONNECTOR_ID].includes(record?.connectorId);
582
+ }
583
+
578
584
  async function persistRecord(record) {
579
585
  // 存储成功后才更新内存,避免落盘失败却在当前进程显示为“已安装”。
580
586
  if (config.persistSecrets) await connectionStore.put(record);
@@ -2200,6 +2206,7 @@ export async function apply(ctx, config) {
2200
2206
  name: record.name,
2201
2207
  connectorId: record.connectorId,
2202
2208
  kind: record.kind,
2209
+ canRename: connectionDisplayNameEditable(record),
2203
2210
  serverName: record.serverName,
2204
2211
  url: record.url,
2205
2212
  transport: record.transport,
@@ -2362,6 +2369,50 @@ export async function apply(ctx, config) {
2362
2369
  };
2363
2370
  },
2364
2371
 
2372
+ async renameConnection(key, name) {
2373
+ const record = state.connections.get(key);
2374
+ if (!record) return { ok: false, message: `连接 "${key}" 不存在` };
2375
+ if (!connectionDisplayNameEditable(record)) {
2376
+ return { ok: false, message: '仅用户自定义或 JSON 导入的连接可修改显示名;市场连接器名称由目录维护' };
2377
+ }
2378
+
2379
+ let nextName;
2380
+ try {
2381
+ nextName = normalizeConnectionDisplayName(name);
2382
+ } catch (error) {
2383
+ return { ok: false, message: `重命名失败: ${error.message}` };
2384
+ }
2385
+ if (nextName === record.name) {
2386
+ return {
2387
+ ok: true,
2388
+ message: `连接显示名未变更:"${record.name}"`,
2389
+ detail: { key: record.key, name: record.name, serverName: record.serverName, unchanged: true, snapshotId: null },
2390
+ };
2391
+ }
2392
+
2393
+ let snapshot;
2394
+ try {
2395
+ snapshot = await captureConnectionSnapshot('rename', [key]);
2396
+ const next = { ...record, name: nextName, updatedAt: Date.now() };
2397
+ const persisted = config.persistSecrets ? await connectionStore.put(next) : next;
2398
+ state.connections.set(key, persisted);
2399
+ return {
2400
+ ok: true,
2401
+ message: `已将连接显示名修改为 "${persisted.name}"`,
2402
+ detail: {
2403
+ key: persisted.key,
2404
+ name: persisted.name,
2405
+ serverName: persisted.serverName,
2406
+ enabled: persisted.enabled !== false,
2407
+ snapshotId: snapshot?.key ?? null,
2408
+ },
2409
+ };
2410
+ } catch (error) {
2411
+ await discardConnectionSnapshot(snapshot);
2412
+ return { ok: false, message: `重命名失败,已保留原显示名: ${error.message}` };
2413
+ }
2414
+ },
2415
+
2365
2416
  async setEnabled(key, enabled) {
2366
2417
  const record = state.connections.get(key);
2367
2418
  if (!record) return { ok: false, message: `连接 "${key}" 不存在` };
package/lib/tools.js CHANGED
@@ -469,6 +469,25 @@ export function registerTools(ctx, api) {
469
469
  },
470
470
  });
471
471
 
472
+ reg({
473
+ name: 'mcp_connector_rename',
474
+ description:
475
+ '修改用户自定义或 JSON 导入 MCP 连接的显示名。不改变 connection key、serverName、工具前缀、运行状态或凭据。',
476
+ parameters: {
477
+ type: 'object',
478
+ properties: {
479
+ key: { type: 'string', description: '连接 key(来自 mcp_connector_status)' },
480
+ name: { type: 'string', description: '新的连接显示名,最长 80 个字符' },
481
+ },
482
+ required: ['key', 'name'],
483
+ additionalProperties: false,
484
+ },
485
+ output: resultOutput,
486
+ async execute(args) {
487
+ return api.renameConnection(args.key, args.name);
488
+ },
489
+ });
490
+
472
491
  reg({
473
492
  name: 'mcp_connector_disconnect',
474
493
  description: '断开并移除一条 MCP 连接;OAuth 连接会按引用计数撤销 refresh_token(无其他连接引用该授权时)。',
package/lib/util.js CHANGED
@@ -52,6 +52,15 @@ export function slugServerName(input) {
52
52
  return slug;
53
53
  }
54
54
 
55
+ /** 归一化用户可编辑的连接显示名;不用于 serverName / 工具前缀。 */
56
+ export function normalizeConnectionDisplayName(input) {
57
+ const name = String(input ?? '').trim();
58
+ if (!name) throw new Error('连接显示名不能为空');
59
+ if (/[\u0000-\u001f\u007f]/.test(name)) throw new Error('连接显示名不能包含控制字符');
60
+ if ([...name].length > 80) throw new Error('连接显示名不能超过 80 个字符');
61
+ return name;
62
+ }
63
+
55
64
  function parseUrl(rawUrl) {
56
65
  let url;
57
66
  try {
package/lib/web.js CHANGED
@@ -41,6 +41,7 @@ const API_WHITELIST = new Map([
41
41
  ['previewSnapshot', (api, p) => api.previewSnapshot(p?.snapshotId)],
42
42
  ['restoreSnapshot', (api, p) => api.restoreSnapshot(p?.snapshotId)],
43
43
  ['installFromUrl', (api, p) => api.installFromUrl(p?.url, p ?? {})],
44
+ ['renameConnection', (api, p) => api.renameConnection(p?.key, p?.name)],
44
45
  ['disconnect', (api, p) => api.disconnect(p?.key)],
45
46
  ['setEnabled', (api, p) => api.setEnabled(p?.key, p?.enabled !== false)],
46
47
  ['refreshCatalog', (api) => api.refreshCatalog()],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mcp-connector",
3
- "version": "0.2.39",
3
+ "version": "0.2.40",
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
@@ -1111,6 +1111,7 @@
1111
1111
 
1112
1112
  function rowHtml(r) {
1113
1113
  const key = esc(r.key);
1114
+ const canRename = r.canRename === true;
1114
1115
  const authMode = r.authMode === 'oauth' ? '<span class="badge auth-oauth">OAuth</span>' : `<span class="badge">${esc(r.authMode || 'manual')}</span>`;
1115
1116
  const healthLabels = { healthy: '已连接', unknown: '状态未知', configured: '已配置', reauth: '需重新授权', recovering: '自动重试中', degraded: '部分异常', unavailable: '连接异常', disabled: '已停用' };
1116
1117
  const healthClass = r.connectionState === 'healthy' ? 'pass' : ['reauth', 'recovering', 'degraded'].includes(r.connectionState) ? 'partial' : r.connectionState === 'unavailable' ? 'fail' : '';
@@ -1131,6 +1132,7 @@
1131
1132
  <input type="checkbox" ${enabled ? 'checked' : ''} onchange="window.__mcp.toggle('${key}', this.checked)">
1132
1133
  <span class="slider"></span>
1133
1134
  </label>
1135
+ ${canRename ? `<button class="btn ghost" type="button" onclick="window.__mcp.openRename('${key}')">重命名</button>` : ''}
1134
1136
  <button class="btn ghost" type="button" onclick="window.__mcp.openScope('${key}')">范围</button>
1135
1137
  <button class="btn danger" onclick="window.__mcp.disconnect('${key}')">断开</button>
1136
1138
  </div>`;
@@ -1244,6 +1246,21 @@
1244
1246
  <div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn" type="button" onclick="window.__mcp.submitScopeChange()">预览并应用</button></div>`);
1245
1247
  }
1246
1248
 
1249
+ function openRenameConnection(key) {
1250
+ const item = installedItems.find((candidate) => candidate.key === key);
1251
+ if (!item) { showToast('连接不存在,请刷新后重试', false); return; }
1252
+ if (item.canRename !== true) {
1253
+ showToast('市场连接器名称由目录维护', false);
1254
+ return;
1255
+ }
1256
+ openModal('修改连接显示名', `
1257
+ <div class="form-note"><strong>只修改页面显示名</strong>当前连接不会中断;<code>serverName</code>、工具前缀、作用范围和凭据均保持不变。</div>
1258
+ <label for="rename-name">连接显示名(必填)</label><input id="rename-name" value="${esc(item.name)}" maxlength="80" autocomplete="off" />
1259
+ <label for="rename-server-name">serverName(工具前缀,不可修改)</label><input id="rename-server-name" value="${esc(item.serverName || '')}" disabled />
1260
+ <div class="inline-error" id="modal-error" tabindex="-1"></div>
1261
+ <div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn" type="button" onclick="window.__mcp.submitRename('${esc(key)}')">保存显示名</button></div>`);
1262
+ }
1263
+
1247
1264
  function openAddConnection(mode = 'json', preset = {}) {
1248
1265
  const server = (preset.servers || [{}])[0] || {};
1249
1266
  if (mode === 'manual' && preset.id && ['bearer', 'api-key'].includes(preset.authMode)) {
@@ -1371,6 +1388,18 @@
1371
1388
  if (connectorId) return window.__mcp.connect(connectorId);
1372
1389
  },
1373
1390
  openScope(key) { openScopeManager(key); },
1391
+ openRename(key) { openRenameConnection(key); },
1392
+ async submitRename(key) {
1393
+ const name = $('#rename-name')?.value.trim() || '';
1394
+ if (!name) { showModalError('连接显示名不能为空'); return; }
1395
+ setModalBusy(true);
1396
+ const result = await call('renameConnection', { key, name });
1397
+ setModalBusy(false);
1398
+ if (!result.ok) { showModalError(result.message); return; }
1399
+ closeModal();
1400
+ showToast(result.message, true);
1401
+ await loadInstalled();
1402
+ },
1374
1403
  async submitScopeChange() {
1375
1404
  if (!pendingScopeKey) return;
1376
1405
  let target;