dsh-mcp-connector 0.2.8 → 0.2.9
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 +9 -1
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/mcp-http.js +34 -13
- package/package.json +1 -1
- package/ui/index.html +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.9] - 2026-08-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `tools/list` 现在在同一 MCP 会话内自动跟随 `nextCursor` 加载全部分页,并对重复游标、超过 100 页或 10,000 个工具的异常目录执行安全阻断。
|
|
12
|
+
- 工具详情统一使用中文服务计数;自动识别服务商返回的 deprecated 描述,将弃用别名排到末尾并标注“已弃用”。
|
|
13
|
+
|
|
7
14
|
## [0.2.8] - 2026-08-22
|
|
8
15
|
|
|
9
16
|
### Fixed
|
|
@@ -187,7 +194,8 @@
|
|
|
187
194
|
- 外部 URL 与导入 Header 执行安全校验。
|
|
188
195
|
- iframe 消息校验同源和消息来源。
|
|
189
196
|
|
|
190
|
-
[Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.
|
|
197
|
+
[Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.9...HEAD
|
|
198
|
+
[0.2.9]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.8...v0.2.9
|
|
191
199
|
[0.2.8]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.7...v0.2.8
|
|
192
200
|
[0.2.7]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.6...v0.2.7
|
|
193
201
|
[0.2.6]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.5...v0.2.6
|
package/README.en.md
CHANGED
|
@@ -110,7 +110,7 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](d
|
|
|
110
110
|
- Failed API key/token validation is not persisted; authentication, timeout, DNS, and TLS/network errors are reported separately.
|
|
111
111
|
- External URLs must use HTTPS; HTTP is allowed only for loopback development.
|
|
112
112
|
- Remote descriptors and catalogs are limited to 2 MiB, Web API requests to 1 MiB, and imported JSON is scanned for credential fields before normalization.
|
|
113
|
-
- Streamable HTTP is the primary transport
|
|
113
|
+
- Streamable HTTP is the primary transport. Legacy SSE entries can still be provisioned to the DSH Host, while live health checks, tool discovery, and pagination in the connector UI target Streamable HTTP. stdio entries are explicitly skipped.
|
|
114
114
|
- The primary sidebar placement uses the stable DSH `data-slot` marker and falls back to the footer if that marker is removed.
|
|
115
115
|
|
|
116
116
|
## License
|
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
|
|
|
108
108
|
- 市场 Key/Token 校验失败时不写入 storage domain;鉴权、超时、DNS、TLS/网络错误会分类提示。
|
|
109
109
|
- 外部 URL 仅允许 HTTPS,HTTP 仅允许回环地址;导入配置会校验 URL 与 Header。
|
|
110
110
|
- 远程目录/描述响应限制 2 MiB,Web API 请求限制 1 MiB;原始 JSON 在归一化前扫描凭据字段。
|
|
111
|
-
- 当前以
|
|
111
|
+
- 当前以 Streamable HTTP 为主;可把旧 SSE 条目交给 DSH Host 兼容运行,但详情页的实时健康检查、工具枚举和分页仅面向 Streamable HTTP。stdio 配置会被明确跳过。
|
|
112
112
|
- 顶部入口通过 DSH 稳定 `data-slot` 定位并使用 React Portal;DSH 若移除该标记,入口会回退到底部,不影响连接器功能。
|
|
113
113
|
- 旧授权迁移必须显式确认,只复制不删除;确认新连接可用后再手动停用旧插件。
|
|
114
114
|
|
package/lib/mcp-http.js
CHANGED
|
@@ -9,6 +9,8 @@ import { MCP_PROTOCOL_VERSION } from './constants.js';
|
|
|
9
9
|
import { authHeaders } from './mcp-provision.js';
|
|
10
10
|
|
|
11
11
|
const MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
12
|
+
const MAX_TOOL_PAGES = 100;
|
|
13
|
+
const MAX_TOOLS = 10_000;
|
|
12
14
|
|
|
13
15
|
export class McpHttpError extends Error {
|
|
14
16
|
constructor(message, { kind = 'protocol', status } = {}) {
|
|
@@ -150,22 +152,41 @@ export async function listMcpTools(record, {
|
|
|
150
152
|
method: 'notifications/initialized',
|
|
151
153
|
params: {},
|
|
152
154
|
}, { grants, sessionId, protocolVersion, signal: controller.signal, fetchImpl });
|
|
153
|
-
if (notification.response.
|
|
155
|
+
if (!notification.response.ok) {
|
|
154
156
|
throw responseError(notification.response, notification.payload, notification.text);
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
159
|
+
const tools = [];
|
|
160
|
+
const seenCursors = new Set();
|
|
161
|
+
let cursor;
|
|
162
|
+
for (let page = 0; page < MAX_TOOL_PAGES; page += 1) {
|
|
163
|
+
const listed = await post(record, {
|
|
164
|
+
jsonrpc: '2.0',
|
|
165
|
+
id: page + 2,
|
|
166
|
+
method: 'tools/list',
|
|
167
|
+
params: cursor ? { cursor } : {},
|
|
168
|
+
}, { grants, sessionId, protocolVersion, signal: controller.signal, fetchImpl });
|
|
169
|
+
if (!listed.response.ok) throw responseError(listed.response, listed.payload, listed.text);
|
|
170
|
+
const listPayloadError = payloadError(listed.payload);
|
|
171
|
+
if (listPayloadError) throw listPayloadError;
|
|
172
|
+
const pageTools = listed.payload?.result?.tools;
|
|
173
|
+
if (!Array.isArray(pageTools)) throw new McpHttpError('MCP tools/list 未返回工具数组');
|
|
174
|
+
tools.push(...pageTools);
|
|
175
|
+
if (tools.length > MAX_TOOLS) {
|
|
176
|
+
throw new McpHttpError(`MCP 工具数量超过安全上限(${MAX_TOOLS})`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const nextCursor = listed.payload?.result?.nextCursor;
|
|
180
|
+
if (typeof nextCursor !== 'string' || nextCursor.length === 0) {
|
|
181
|
+
return { tools, protocolVersion, sessionful: Boolean(sessionId), pages: page + 1 };
|
|
182
|
+
}
|
|
183
|
+
if (seenCursors.has(nextCursor)) {
|
|
184
|
+
throw new McpHttpError('MCP tools/list 返回了重复分页游标');
|
|
185
|
+
}
|
|
186
|
+
seenCursors.add(nextCursor);
|
|
187
|
+
cursor = nextCursor;
|
|
188
|
+
}
|
|
189
|
+
throw new McpHttpError(`MCP tools/list 分页超过安全上限(${MAX_TOOL_PAGES} 页)`);
|
|
169
190
|
} finally {
|
|
170
191
|
clearTimeout(timer);
|
|
171
192
|
await closeSession(record, { grants, sessionId, protocolVersion, fetchImpl });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mcp-connector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "General-purpose MCP connector, connection manager, plugin extension, and integration marketplace for DeepSeek Harness, initiated and maintained by Qichacha/QCC.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/ui/index.html
CHANGED
|
@@ -290,7 +290,9 @@
|
|
|
290
290
|
.tool-item { padding: 10px 12px; border-top: 1px solid #f0f1f3; }
|
|
291
291
|
.tool-item:first-child { border-top: 0; }
|
|
292
292
|
.tool-item:hover { background: var(--accent-light); }
|
|
293
|
+
.tool-name-row { display: flex; align-items: center; gap: 7px; }
|
|
293
294
|
.tool-name { color: var(--text); font: 600 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; overflow-wrap: anywhere; }
|
|
295
|
+
.tool-deprecated { color: var(--warn); background: var(--warn-bg); border-radius: 999px; padding: 1px 6px; font-size: 10px; line-height: 1.5; flex: 0 0 auto; }
|
|
294
296
|
.tool-description { color: var(--text-2); font-size: 12px; line-height: 1.55; margin-top: 4px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
295
297
|
.tool-description.empty { color: var(--muted); font-style: italic; }
|
|
296
298
|
.tools-loading, .tools-error { font-size: 13px; color: var(--muted); padding: 8px; }
|
|
@@ -617,12 +619,16 @@
|
|
|
617
619
|
|
|
618
620
|
function updateToolsSummary() {
|
|
619
621
|
const totalTools = detailToolServers.reduce((sum, server) => sum + (server.tools?.length || 0), 0);
|
|
620
|
-
$('#tools-summary').textContent = `${detailToolServers.length}
|
|
622
|
+
$('#tools-summary').textContent = `${detailToolServers.length} 个服务 · ${totalTools} 个工具`;
|
|
621
623
|
$('#tools-search').placeholder = totalTools > 0
|
|
622
624
|
? `搜索 ${totalTools} 个工具的名称或描述…`
|
|
623
625
|
: '搜索工具名称或描述…';
|
|
624
626
|
}
|
|
625
627
|
|
|
628
|
+
function isDeprecatedTool(tool) {
|
|
629
|
+
return /(?:\bdeprecated\b|已弃用|已废弃|不再推荐)/i.test(`${tool?.title || ''}\n${tool?.description || ''}`);
|
|
630
|
+
}
|
|
631
|
+
|
|
626
632
|
function renderTools() {
|
|
627
633
|
const query = $('#tools-search').value.trim().toLowerCase();
|
|
628
634
|
if (detailToolServers.length === 0) {
|
|
@@ -642,7 +648,7 @@
|
|
|
642
648
|
const matched = (server.tools || []).filter((tool) => {
|
|
643
649
|
if (!query) return true;
|
|
644
650
|
return `${tool.name || ''}\n${tool.title || ''}\n${tool.description || ''}`.toLowerCase().includes(query);
|
|
645
|
-
});
|
|
651
|
+
}).sort((left, right) => Number(isDeprecatedTool(left)) - Number(isDeprecatedTool(right)));
|
|
646
652
|
if (query && matched.length === 0) return '';
|
|
647
653
|
matchedTools += matched.length;
|
|
648
654
|
const tools = matched.slice(0, Math.max(0, remaining));
|
|
@@ -652,7 +658,8 @@
|
|
|
652
658
|
const items = tools.length > 0
|
|
653
659
|
? tools.map((tool) => {
|
|
654
660
|
const description = tool.description || (tool.title && tool.title !== tool.name ? tool.title : '');
|
|
655
|
-
|
|
661
|
+
const deprecated = isDeprecatedTool(tool);
|
|
662
|
+
return `<div class="tool-item"><div class="tool-name-row"><div class="tool-name">${esc(tool.name || tool.title || '未命名工具')}</div>${deprecated ? '<span class="tool-deprecated">已弃用</span>' : ''}</div><div class="tool-description${description ? '' : ' empty'}">${esc(description || '暂无描述')}</div></div>`;
|
|
656
663
|
}).join('')
|
|
657
664
|
: '<div class="tools-loading">暂无工具</div>';
|
|
658
665
|
return `<div class="tools-server"><div class="tools-server-header">${esc(label)} <span class="count">· 显示 ${tools.length} / ${matched.length} 个</span></div>${items}</div>`;
|