dsh-my-config 1.8.0 → 1.8.1
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/README.md +5 -0
- package/index.js +58 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,11 @@ DeepSeek Harness 插件:在 Web 侧边栏提供「我的技能 / 我的连接
|
|
|
12
12
|
|
|
13
13
|
## 更新日志
|
|
14
14
|
|
|
15
|
+
### v1.8.1
|
|
16
|
+
|
|
17
|
+
- **修复**:streamable-http「测试连接」适配 stateful 服务端——带回 `mcp-session-id`(TS SDK 服务端强校验,缺失返回 400);响应支持 gzip / deflate / br 解码(部分 CDN 强制压缩导致解析失败);initialize 协议版本不匹配时自动降级重试。
|
|
18
|
+
- **修复**:Windows 下 stdio 探测无法启动 `npx` / `uvx` 等 `.cmd` shim 的问题(改经 cmd.exe 启动,与内核 MCP SDK 行为一致)。
|
|
19
|
+
|
|
15
20
|
### v1.8.0
|
|
16
21
|
|
|
17
22
|
- **技能**:编辑器支持 license / compatibility / metadata / allowed-tools 全字段(对齐 agentskills.io 规范);保存与导入时执行规范校验,错误逐字段定位展示。
|
package/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { promises as fs } from 'node:fs';
|
|
14
14
|
import os from 'node:os';
|
|
15
15
|
import { join, resolve, sep, basename } from 'node:path';
|
|
16
|
-
import { inflateRawSync } from 'node:zlib';
|
|
16
|
+
import { inflateRawSync, gunzipSync, inflateSync, brotliDecompressSync } from 'node:zlib';
|
|
17
17
|
import { spawn } from 'node:child_process';
|
|
18
18
|
import http from 'node:http';
|
|
19
19
|
import https from 'node:https';
|
|
@@ -635,8 +635,12 @@ function normalizeToolList(list) {
|
|
|
635
635
|
})).filter((t) => t.name);
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
-
/**
|
|
639
|
-
|
|
638
|
+
/**
|
|
639
|
+
* streamable-http 单次 JSON-RPC POST;兼容 text/event-stream 与 gzip/deflate 响应。
|
|
640
|
+
* 返回 { msg, sessionId }:msg 为解析后的 JSON-RPC 消息;sessionId 取响应 mcp-session-id
|
|
641
|
+
* 头(stateful 服务端在 initialize 后要求后续请求带回,缺失会被 400 拒绝)。
|
|
642
|
+
*/
|
|
643
|
+
function httpJsonRpcPost(urlString, headers, payload, timeoutMs, sessionId) {
|
|
640
644
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
641
645
|
let target;
|
|
642
646
|
try { target = new URL(urlString); } catch { rejectPromise(new Error('url 不是合法地址')); return; }
|
|
@@ -647,6 +651,8 @@ function httpJsonRpcPost(urlString, headers, payload, timeoutMs) {
|
|
|
647
651
|
headers: {
|
|
648
652
|
'content-type': 'application/json',
|
|
649
653
|
accept: 'application/json, text/event-stream',
|
|
654
|
+
'accept-encoding': 'identity', // 明示不压缩;部分 CDN 仍强推 gzip,响应侧再做兼容解码
|
|
655
|
+
...(sessionId ? { 'mcp-session-id': sessionId } : {}),
|
|
650
656
|
...(headers && typeof headers === 'object' ? headers : {}),
|
|
651
657
|
'content-length': Buffer.byteLength(body),
|
|
652
658
|
},
|
|
@@ -655,20 +661,36 @@ function httpJsonRpcPost(urlString, headers, payload, timeoutMs) {
|
|
|
655
661
|
const chunks = [];
|
|
656
662
|
res.on('data', (chunk) => chunks.push(chunk));
|
|
657
663
|
res.on('end', () => {
|
|
658
|
-
|
|
664
|
+
let raw = Buffer.concat(chunks);
|
|
665
|
+
try {
|
|
666
|
+
const enc = String(res.headers['content-encoding'] ?? '').toLowerCase();
|
|
667
|
+
if (enc === 'gzip') raw = gunzipSync(raw);
|
|
668
|
+
else if (enc === 'deflate') raw = inflateSync(raw);
|
|
669
|
+
else if (enc === 'br') raw = brotliDecompressSync(raw);
|
|
670
|
+
} catch (err) {
|
|
671
|
+
rejectPromise(new Error(`响应压缩解码失败(${String(res.headers['content-encoding'])}): ${err?.message ?? err}`));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const text = raw.toString('utf8');
|
|
659
675
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
660
676
|
rejectPromise(new Error(`HTTP ${res.statusCode}: ${text.slice(0, 200)}`));
|
|
661
677
|
return;
|
|
662
678
|
}
|
|
679
|
+
const nextSid = res.headers['mcp-session-id'] ?? sessionId;
|
|
663
680
|
try {
|
|
664
681
|
if (String(res.headers['content-type'] ?? '').includes('text/event-stream')) {
|
|
665
|
-
const dataLines = text.split(/\r?\n/).filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim());
|
|
666
|
-
|
|
682
|
+
const dataLines = text.split(/\r?\n/).filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()).filter(Boolean);
|
|
683
|
+
let msg;
|
|
684
|
+
for (let i = dataLines.length - 1; i >= 0; i--) {
|
|
685
|
+
try { msg = JSON.parse(dataLines[i]); break; } catch { /* 继续往前找 */ }
|
|
686
|
+
}
|
|
687
|
+
if (msg === undefined) throw new Error('no-json');
|
|
688
|
+
resolvePromise({ msg, sessionId: nextSid });
|
|
667
689
|
} else {
|
|
668
|
-
resolvePromise(JSON.parse(text));
|
|
690
|
+
resolvePromise({ msg: JSON.parse(text), sessionId: nextSid });
|
|
669
691
|
}
|
|
670
692
|
} catch {
|
|
671
|
-
rejectPromise(new Error(
|
|
693
|
+
rejectPromise(new Error(`响应不是合法 JSON:${text.slice(0, 120)}`));
|
|
672
694
|
}
|
|
673
695
|
});
|
|
674
696
|
});
|
|
@@ -683,24 +705,32 @@ function jsonRpcErrorMessage(msg) {
|
|
|
683
705
|
return msg?.error?.message ? String(msg.error.message) : 'JSON-RPC 返回错误';
|
|
684
706
|
}
|
|
685
707
|
|
|
686
|
-
/** streamable-http 探测:initialize → notifications/initialized → tools/list。
|
|
708
|
+
/** streamable-http 探测:initialize → notifications/initialized → tools/list。
|
|
709
|
+
* 带回 mcp-session-id(stateful 服务端强校验);initialize 报协议版本错误时降级重试旧版本。 */
|
|
687
710
|
async function probeHttpConnector(record, timeoutMs) {
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
711
|
+
const tryInit = async (protocolVersion) => {
|
|
712
|
+
const init = await httpJsonRpcPost(record.url, record.headers, {
|
|
713
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
714
|
+
params: {
|
|
715
|
+
protocolVersion,
|
|
716
|
+
capabilities: {},
|
|
717
|
+
clientInfo: { name: 'dsh-manager-center', version: '1.8.1' },
|
|
718
|
+
},
|
|
719
|
+
}, timeoutMs);
|
|
720
|
+
return init;
|
|
695
721
|
};
|
|
696
|
-
|
|
697
|
-
if (init?.error
|
|
722
|
+
let init = await tryInit('2025-06-18');
|
|
723
|
+
if (init.msg?.error && /protocol/i.test(String(init.msg.error.message ?? ''))) {
|
|
724
|
+
init = await tryInit('2024-11-05');
|
|
725
|
+
}
|
|
726
|
+
if (init.msg?.error) throw new Error(jsonRpcErrorMessage(init.msg));
|
|
727
|
+
const sessionId = init.sessionId; // stateful 服务端在 initialize 响应头发 mcp-session-id
|
|
698
728
|
try {
|
|
699
|
-
await httpJsonRpcPost(record.url, record.headers, { jsonrpc: '2.0', method: 'notifications/initialized' }, timeoutMs);
|
|
729
|
+
await httpJsonRpcPost(record.url, record.headers, { jsonrpc: '2.0', method: 'notifications/initialized' }, timeoutMs, sessionId);
|
|
700
730
|
} catch { /* initialized 通知失败不阻断(部分服务端不要求) */ }
|
|
701
|
-
const list = await httpJsonRpcPost(record.url, record.headers, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }, timeoutMs);
|
|
702
|
-
if (list?.error) throw new Error(jsonRpcErrorMessage(list));
|
|
703
|
-
return normalizeToolList(list?.result?.tools);
|
|
731
|
+
const list = await httpJsonRpcPost(record.url, record.headers, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }, timeoutMs, sessionId);
|
|
732
|
+
if (list.msg?.error) throw new Error(jsonRpcErrorMessage(list.msg));
|
|
733
|
+
return normalizeToolList(list.msg?.result?.tools);
|
|
704
734
|
}
|
|
705
735
|
|
|
706
736
|
/**
|
|
@@ -761,13 +791,17 @@ function probeStdioTransport(child, timeoutMs) {
|
|
|
761
791
|
});
|
|
762
792
|
}
|
|
763
793
|
|
|
764
|
-
/** stdio 连接器探测:spawn 子进程(env 合并 process.env),再走统一传输层。绝不同步抛出。
|
|
794
|
+
/** stdio 连接器探测:spawn 子进程(env 合并 process.env),再走统一传输层。绝不同步抛出。
|
|
795
|
+
* Windows 兼容:npx/uvx 等是 .cmd 脚本 shim,CreateProcess 无法直接执行,
|
|
796
|
+
* 无扩展名命令统一经 shell(cmd.exe /d /s /c)启动,与内核 MCP SDK 行为一致。 */
|
|
765
797
|
function probeStdioConnector(record, timeoutMs) {
|
|
766
798
|
let child;
|
|
767
799
|
try {
|
|
768
|
-
|
|
800
|
+
const command = String(record.command ?? '');
|
|
801
|
+
child = spawn(command, Array.isArray(record.args) ? record.args : [], {
|
|
769
802
|
env: { ...process.env, ...(record.env && typeof record.env === 'object' ? record.env : {}) },
|
|
770
803
|
...(record.cwd ? { cwd: record.cwd } : {}),
|
|
804
|
+
...(process.platform === 'win32' && !/\.[a-z0-9]+$/i.test(command) ? { shell: true } : {}),
|
|
771
805
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
772
806
|
});
|
|
773
807
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-my-config",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "My Config (我的配置): manage skills (with external skill markets and zip import), MCP connectors and agent experts from dedicated Web sidebar panels.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|