dsh-mcp-connector 0.2.76 → 0.2.77
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 +8 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/ui/index.html +39 -28
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/);首个公开版本从 `0.1.0` 开始。
|
|
4
4
|
|
|
5
|
+
## [0.2.77] - 2026-10-22
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
|
|
9
|
+
- 从 PR #174 独立提取内联事件参数转义修复:先编码 JavaScript 字符串,再编码 HTML 属性,防止目录/连接标识中的特殊字符形成脚本注入。感谢 Marek Vrbka 的报告与修复。
|
|
10
|
+
- 回归测试直接读取生产 UI 的转义函数;隔离真实浏览器通过 28 个事件/载荷组合,并复现旧转义方式的对照问题。验证不访问真实 DSH API、凭据或用户连接。
|
|
11
|
+
- 本次不包含 #174 的国际化改造;既有 Windows Desktop 等验收待办保持开放。
|
|
12
|
+
|
|
5
13
|
## [0.2.76] - 2026-10-20
|
|
6
14
|
|
|
7
15
|
### Added / Fixed
|
package/README.en.md
CHANGED
|
@@ -225,7 +225,7 @@ npm run dev:ui
|
|
|
225
225
|
|
|
226
226
|
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.
|
|
227
227
|
|
|
228
|
-
The current public version is [`dsh-mcp-connector@0.2.
|
|
228
|
+
The current public version is [`dsh-mcp-connector@0.2.77`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.77](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.77).
|
|
229
229
|
|
|
230
230
|
See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
|
|
231
231
|
|
package/README.md
CHANGED
|
@@ -220,7 +220,7 @@ npm run dev:ui
|
|
|
220
220
|
|
|
221
221
|
公共 Registry 每次合并后会生成 `catalog-stats.json`;本仓库的定时工作流每小时同步中英文介绍和统计快照。npm 页面中的静态正文随版本发布更新,上方动态统计徽标则直接读取 Registry,可在不发布新 npm 版本时保持实时数量一致。
|
|
222
222
|
|
|
223
|
-
当前公开版本为 [`dsh-mcp-connector@0.2.
|
|
223
|
+
当前公开版本为 [`dsh-mcp-connector@0.2.77`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.77](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.77)。
|
|
224
224
|
|
|
225
225
|
版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
|
|
226
226
|
Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mcp-connector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.77",
|
|
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
|
@@ -736,6 +736,15 @@
|
|
|
736
736
|
return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
737
737
|
}
|
|
738
738
|
|
|
739
|
+
// 内联事件属性里的字符串实参必须是「JS 字符串字面量」再按 HTML 属性转义:
|
|
740
|
+
// HTML 解析器会先把 ' 解回 ',所以只做 HTML 转义会让单引号逃逸出字符串,
|
|
741
|
+
// 形成 stored XSS(例如 id 为 x');alert(1)// 的目录条目)。
|
|
742
|
+
// JSON.stringify 负责 JS 层转义(引号、反斜杠、控制字符),esc 负责属性层转义,
|
|
743
|
+
// 两层配合后无论值里有什么,浏览器解析出来都只是一个普通字符串字面量。
|
|
744
|
+
function jsString(value) {
|
|
745
|
+
return esc(JSON.stringify(String(value ?? '')));
|
|
746
|
+
}
|
|
747
|
+
|
|
739
748
|
function isImageIcon(value) {
|
|
740
749
|
return typeof value === 'string' && /^(?:https?:|data:image\/|\/)/.test(value);
|
|
741
750
|
}
|
|
@@ -1399,7 +1408,7 @@
|
|
|
1399
1408
|
const probe = d.probeStatus && d.probeStatus !== 'unverified' ? `<span class="badge ${esc(d.probeStatus)}">${d.probeStatus}</span>` : '';
|
|
1400
1409
|
const runtimeLabel = d.id === 'dingtalk' ? '本地 CLI' : (d.servers || []).some((s) => s.transport === 'stdio') ? '本地运行' : '';
|
|
1401
1410
|
const runtimeTag = runtimeLabel ? `<span class="runtime-tag" title="包含运行于 DSH 主机的服务;授权和权限要求请查看详情">${runtimeLabel}</span>` : '';
|
|
1402
|
-
return `<div class="card ${d.featured ? 'featured' : ''}" data-id="${esc(d.id)}" role="button" tabindex="0" aria-label="查看 ${esc(d.name)} 详情" onclick="window.__mcp.showDetail(
|
|
1411
|
+
return `<div class="card ${d.featured ? 'featured' : ''}" data-id="${esc(d.id)}" role="button" tabindex="0" aria-label="查看 ${esc(d.name)} 详情" onclick="window.__mcp.showDetail(${jsString(d.id)})" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();window.__mcp.showDetail(${jsString(d.id)})}">
|
|
1403
1412
|
<div class="icon">${icon}</div>
|
|
1404
1413
|
<div class="content">
|
|
1405
1414
|
<div class="name">${esc(d.name)} ${probe} ${runtimeTag}</div>
|
|
@@ -1411,36 +1420,37 @@
|
|
|
1411
1420
|
|
|
1412
1421
|
function actionHtml(d) {
|
|
1413
1422
|
const id = esc(d.id);
|
|
1423
|
+
const idJs = jsString(d.id);
|
|
1414
1424
|
const configured = d.connected?.length > 0;
|
|
1415
1425
|
const incomplete = configured && d.connectionComplete === false;
|
|
1416
1426
|
const state = d.connectionState || (configured ? 'configured' : 'disconnected');
|
|
1417
1427
|
if (incomplete && ['bearer', 'api-key'].includes(d.authMode)) {
|
|
1418
|
-
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1428
|
+
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.openConfig(${idJs})">${t('completeSetup')}</button>`;
|
|
1419
1429
|
}
|
|
1420
1430
|
if (incomplete && d.authMode === 'oauth2-pkce') {
|
|
1421
|
-
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1431
|
+
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.connect(${idJs})">${t('upgradeOauth')}</button>`;
|
|
1422
1432
|
}
|
|
1423
1433
|
if (configured) {
|
|
1424
|
-
if (state === 'healthy') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1425
|
-
if (state === 'unknown') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1426
|
-
if (state === 'configured') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1427
|
-
if (state === 'disabled') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1434
|
+
if (state === 'healthy') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn ghost" disabled>${t('connected')}</button>`;
|
|
1435
|
+
if (state === 'unknown') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('unknown')}</button>`;
|
|
1436
|
+
if (state === 'configured') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn ghost" disabled>${t('configured')}</button>`;
|
|
1437
|
+
if (state === 'disabled') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn ghost" disabled>${t('disabled')}</button>`;
|
|
1428
1438
|
if (state === 'reauth') {
|
|
1429
1439
|
const credentialAuth = ['bearer', 'api-key'].includes(d.authMode);
|
|
1430
|
-
const
|
|
1431
|
-
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1440
|
+
const reauthAction = credentialAuth ? `window.__mcp.openConfig(${idJs})` : `window.__mcp.showDetail(${idJs})`;
|
|
1441
|
+
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();${reauthAction}">${credentialAuth ? t('reconfigureCredential') : t('reauthorize')}</button>`;
|
|
1432
1442
|
}
|
|
1433
|
-
if (state === 'recovering') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1434
|
-
if (state === 'degraded') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1435
|
-
if (state === 'unavailable') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1443
|
+
if (state === 'recovering') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('recovering')}</button>`;
|
|
1444
|
+
if (state === 'degraded') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn status-warning" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('partialError')}</button>`;
|
|
1445
|
+
if (state === 'unavailable') return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn status-bad" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('connectionError')}</button>`;
|
|
1436
1446
|
}
|
|
1437
1447
|
if (d.authMode === 'oauth2-pkce') {
|
|
1438
|
-
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1448
|
+
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn" type="button" onclick="event.stopPropagation();window.__mcp.connect(${idJs})">${t('connect')}</button>`;
|
|
1439
1449
|
}
|
|
1440
1450
|
if (d.authMode === 'none') {
|
|
1441
|
-
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1451
|
+
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn" type="button" onclick="event.stopPropagation();window.__mcp.connect(${idJs})">${t('connect')}</button>`;
|
|
1442
1452
|
}
|
|
1443
|
-
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(
|
|
1453
|
+
return `<button class="btn ghost" type="button" onclick="event.stopPropagation();window.__mcp.showDetail(${idJs})">${t('details')}</button><button class="btn" type="button" onclick="event.stopPropagation();window.__mcp.openConfig(${idJs})">${t('configure')}</button>`;
|
|
1444
1454
|
}
|
|
1445
1455
|
|
|
1446
1456
|
const EXPLORER_STATUS = { pending: '尚未发现', unknown: 'Host 状态未知', success: '最近发现成功', failed: '最近发现失败' };
|
|
@@ -1545,6 +1555,7 @@
|
|
|
1545
1555
|
|
|
1546
1556
|
function rowHtml(r) {
|
|
1547
1557
|
const key = esc(r.key);
|
|
1558
|
+
const keyJs = jsString(r.key);
|
|
1548
1559
|
const canRename = r.canRename === true;
|
|
1549
1560
|
const canEditConfiguration = r.canEditConfiguration === true;
|
|
1550
1561
|
const authMode = r.authMode === 'oauth' ? '<span class="badge auth-oauth">OAuth</span>' : `<span class="badge">${esc(r.authMode || 'manual')}</span>`;
|
|
@@ -1564,21 +1575,21 @@
|
|
|
1564
1575
|
<div class="rmeta">${esc(r.serverName || '')} · ${esc(r.endpoint || r.url || '')}</div>
|
|
1565
1576
|
${diagnostic}
|
|
1566
1577
|
</div>
|
|
1567
|
-
<select class="injection-select" aria-label="${esc(r.name)} 工具注入模式" title="按会话模式不会断开连接,只会默认隐藏业务工具 schema" onchange="window.__mcp.changeInjectionMode(
|
|
1578
|
+
<select class="injection-select" aria-label="${esc(r.name)} 工具注入模式" title="按会话模式不会断开连接,只会默认隐藏业务工具 schema" onchange="window.__mcp.changeInjectionMode(${keyJs}, this.value, this)">
|
|
1568
1579
|
<option value="always" ${injectionMode === 'always' ? 'selected' : ''}>始终注入</option>
|
|
1569
1580
|
<option value="session" ${injectionMode === 'session' ? 'selected' : ''}>按会话启用(实验)</option>
|
|
1570
1581
|
</select>
|
|
1571
1582
|
<label class="switch" title="${enabled ? '停用' : '启用'}" aria-label="${enabled ? '停用' : '启用'} ${esc(r.name)}">
|
|
1572
|
-
<input type="checkbox" ${enabled ? 'checked' : ''} onchange="window.__mcp.toggle(
|
|
1583
|
+
<input type="checkbox" ${enabled ? 'checked' : ''} onchange="window.__mcp.toggle(${keyJs}, this.checked)">
|
|
1573
1584
|
<span class="slider"></span>
|
|
1574
1585
|
</label>
|
|
1575
|
-
${canEditConfiguration ? `<button class="btn ghost" type="button" onclick="window.__mcp.openEditor(
|
|
1576
|
-
${r.canEditLocalOverrides ? `<button class="btn ghost" type="button" onclick="window.__mcp.openLocalEditor(
|
|
1577
|
-
${canRename ? `<button class="btn ghost" type="button" onclick="window.__mcp.openRename(
|
|
1578
|
-
${enabled ? `<button class="btn ghost" type="button" onclick="window.__mcp.showConnectionTools(
|
|
1579
|
-
${enabled && r.connectionState !== 'healthy' ? `<button class="btn ghost" type="button" title="重新建立 Host 侧条目并复检:不改配置、凭据、范围或注入模式" onclick="window.__mcp.reconnect(
|
|
1580
|
-
<button class="btn ghost" type="button" onclick="window.__mcp.openScope(
|
|
1581
|
-
<button class="btn danger" onclick="window.__mcp.disconnect(
|
|
1586
|
+
${canEditConfiguration ? `<button class="btn ghost" type="button" onclick="window.__mcp.openEditor(${keyJs})">编辑配置</button>` : ''}
|
|
1587
|
+
${r.canEditLocalOverrides ? `<button class="btn ghost" type="button" onclick="window.__mcp.openLocalEditor(${keyJs})">本地参数</button>` : r.hasLocalOverrides ? `<button class="btn ghost" type="button" onclick="window.__mcp.submitLocalOverrides(${keyJs}, true)">清除本地参数</button>` : ''}
|
|
1588
|
+
${canRename ? `<button class="btn ghost" type="button" onclick="window.__mcp.openRename(${keyJs})">重命名</button>` : ''}
|
|
1589
|
+
${enabled ? `<button class="btn ghost" type="button" onclick="window.__mcp.showConnectionTools(${keyJs})">工具</button>` : ''}
|
|
1590
|
+
${enabled && r.connectionState !== 'healthy' ? `<button class="btn ghost" type="button" title="重新建立 Host 侧条目并复检:不改配置、凭据、范围或注入模式" onclick="window.__mcp.reconnect(${keyJs})">重新连接</button>` : ''}
|
|
1591
|
+
<button class="btn ghost" type="button" onclick="window.__mcp.openScope(${keyJs})">范围</button>
|
|
1592
|
+
<button class="btn danger" onclick="window.__mcp.disconnect(${keyJs})">断开</button>
|
|
1582
1593
|
</div>`;
|
|
1583
1594
|
}
|
|
1584
1595
|
|
|
@@ -1708,7 +1719,7 @@
|
|
|
1708
1719
|
<label for="rename-name">连接显示名(必填)</label><input id="rename-name" value="${esc(item.name)}" maxlength="80" autocomplete="off" />
|
|
1709
1720
|
<label for="rename-server-name">serverName(工具前缀,不可修改)</label><input id="rename-server-name" value="${esc(item.serverName || '')}" disabled />
|
|
1710
1721
|
<div class="inline-error" id="modal-error" tabindex="-1"></div>
|
|
1711
|
-
<div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn" type="button" onclick="window.__mcp.submitRename(
|
|
1722
|
+
<div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn" type="button" onclick="window.__mcp.submitRename(${jsString(key)})">保存显示名</button></div>`);
|
|
1712
1723
|
}
|
|
1713
1724
|
|
|
1714
1725
|
async function openConnectionEditor(key) {
|
|
@@ -1723,7 +1734,7 @@
|
|
|
1723
1734
|
<textarea id="edit-connection-json" rows="14" spellcheck="false">${esc(result.detail?.json || '')}</textarea>
|
|
1724
1735
|
<div class="field-help">提交前会先验证新配置并创建快照。任何校验、启动或保存失败都会保留原连接;连接 key、作用范围与启停状态不变。</div>
|
|
1725
1736
|
<div class="inline-error" id="modal-error" tabindex="-1"></div>
|
|
1726
|
-
<div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn" type="button" onclick="window.__mcp.submitReconfigure(
|
|
1737
|
+
<div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn" type="button" onclick="window.__mcp.submitReconfigure(${jsString(key)})">${action}</button></div>`);
|
|
1727
1738
|
}
|
|
1728
1739
|
|
|
1729
1740
|
async function openLocalEditor(key) {
|
|
@@ -1737,7 +1748,7 @@
|
|
|
1737
1748
|
<textarea id="local-overrides-json" rows="12" spellcheck="false">${esc(result.detail?.json || '')}</textarea>
|
|
1738
1749
|
<div class="field-help"><code><KEEP_EXISTING></code> 保留本机已有值;删除字段清除该项覆盖。保存会创建快照并重新加载已启用连接,停用连接保持停用;不代表工具调用已验收。</div>
|
|
1739
1750
|
<div class="inline-error" id="modal-error" tabindex="-1"></div>
|
|
1740
|
-
<div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn ghost" type="button" onclick="window.__mcp.submitLocalOverrides(
|
|
1751
|
+
<div class="m-actions"><button class="btn ghost" type="button" onclick="window.__mcp.closeModal()">取消</button><button class="btn ghost" type="button" onclick="window.__mcp.submitLocalOverrides(${jsString(key)}, true)">恢复市场参数</button><button class="btn" type="button" onclick="window.__mcp.submitLocalOverrides(${jsString(key)})">保存本地参数</button></div>`);
|
|
1741
1752
|
}
|
|
1742
1753
|
|
|
1743
1754
|
function openAddConnection(mode = 'json', preset = {}) {
|
|
@@ -1804,7 +1815,7 @@
|
|
|
1804
1815
|
}
|
|
1805
1816
|
const items = snapshots.detail?.items || [];
|
|
1806
1817
|
const snapshotHtml = items.length
|
|
1807
|
-
? `<div class="snapshot-list">${items.map((item) => `<div class="snapshot-item"><div class="snapshot-copy"><strong>${esc(item.reason)} · ${esc(new Date(item.createdAt).toLocaleString('zh-CN', { hour12: false }))}</strong>${item.existingCount} 条连接${item.absentCount ? ` · 可移除 ${item.absentCount} 条变更后新增连接` : ''}${item.restorable ? '' : ' · OAuth 需重新授权'}</div><button class="btn ghost" type="button" ${item.restorable ? '' : 'disabled'} onclick="window.__mcp.restoreBackup(
|
|
1818
|
+
? `<div class="snapshot-list">${items.map((item) => `<div class="snapshot-item"><div class="snapshot-copy"><strong>${esc(item.reason)} · ${esc(new Date(item.createdAt).toLocaleString('zh-CN', { hour12: false }))}</strong>${item.existingCount} 条连接${item.absentCount ? ` · 可移除 ${item.absentCount} 条变更后新增连接` : ''}${item.restorable ? '' : ' · OAuth 需重新授权'}</div><button class="btn ghost" type="button" ${item.restorable ? '' : 'disabled'} onclick="window.__mcp.restoreBackup(${jsString(item.id)})">恢复</button></div>`).join('')}</div>`
|
|
1808
1819
|
: '<div class="field-help">还没有本机配置快照。连接、配置、导入、启停和断开前会自动创建,也可立即手动创建。</div>';
|
|
1809
1820
|
openModal('配置备份', `
|
|
1810
1821
|
<div class="form-note"><strong>可携带脱敏导出</strong>Token、API Key、Header/env 值、stdio 参数、本地路径与 OAuth Grant 均不会导出;占位符需在目标本机重填,OAuth 需重新授权。</div>
|