@wfm-cn/dsh-mcp-connectors 0.2.1 → 0.2.3
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 +2 -0
- package/client.js +1 -1
- package/index.js +51 -11
- package/package.json +1 -1
package/README.md
CHANGED
package/client.js
CHANGED
|
@@ -226,7 +226,7 @@ window.__ModuleLoader__.load({
|
|
|
226
226
|
const wsName = (s) => {
|
|
227
227
|
const cwd = String(s.cwd ?? '');
|
|
228
228
|
if (!cwd) return s.live ? '(活跃)' : '';
|
|
229
|
-
const parts = cwd.split(
|
|
229
|
+
const parts = cwd.split(/[\\/]/).filter(Boolean);
|
|
230
230
|
return parts[parts.length - 1] ?? cwd;
|
|
231
231
|
};
|
|
232
232
|
const label = (s) => s.title || s.id;
|
package/index.js
CHANGED
|
@@ -231,9 +231,20 @@ export async function apply(ctx, config = {}) {
|
|
|
231
231
|
function stdioClient(c) {
|
|
232
232
|
let cl = STDIO_CLIENTS.get(c.serverName);
|
|
233
233
|
if (cl) return cl;
|
|
234
|
-
|
|
234
|
+
// Windows 上 npx/pnpm 等命令是 .cmd shim,必须经 shell 才能启动;
|
|
235
|
+
// macOS/Linux 不加 shell,避免参数被 shell 二次解析
|
|
236
|
+
const isWin = process.platform === 'win32';
|
|
237
|
+
let command = c.command;
|
|
238
|
+
if (isWin) {
|
|
239
|
+
const s = String(command).trim();
|
|
240
|
+
if (/^(npx|npm|pnpm|yarn|bunx?|uvx?|python3?|node)$/i.test(s)) command = `${s}.cmd`;
|
|
241
|
+
else if (/[\\/ ]/.test(s) && !/\.(cmd|exe|bat|ps1)$/i.test(s)) command = s; // 完整路径交给 shell 解析
|
|
242
|
+
}
|
|
243
|
+
const child = spawn(command, c.args ?? [], {
|
|
235
244
|
env: { ...process.env, ...(c.env ?? {}) },
|
|
236
245
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
246
|
+
shell: isWin,
|
|
247
|
+
windowsHide: true,
|
|
237
248
|
});
|
|
238
249
|
child.on('exit', () => STDIO_CLIENTS.delete(c.serverName));
|
|
239
250
|
const pending = new Map();
|
|
@@ -700,30 +711,59 @@ export async function apply(ctx, config = {}) {
|
|
|
700
711
|
for (const [sn, w] of want) {
|
|
701
712
|
if (!MOUNTED.has(sn)) await mount(w);
|
|
702
713
|
}
|
|
714
|
+
// 工具集变化后重新计算各会话的门控(deny 依赖已注册的工具名)
|
|
715
|
+
reapplyGates();
|
|
703
716
|
};
|
|
704
717
|
|
|
705
718
|
/* ---------------- 每会话工具门控 ---------------- */
|
|
706
719
|
|
|
707
|
-
const RESTRICTORS = new Map(); // agentId ->
|
|
720
|
+
const RESTRICTORS = new Map(); // agentId -> disposer
|
|
721
|
+
const GATE_PENDING = new Set(); // 因工具尚未注册而待重试的 agentId
|
|
722
|
+
|
|
723
|
+
/** 当前已挂载连接器的公开工具名(含 mcp__server__ 前缀)。 */
|
|
724
|
+
function mountedToolNamesByServer() {
|
|
725
|
+
const out = new Map(); // serverName -> string[]
|
|
726
|
+
for (const [serverName, rec] of MOUNTED) {
|
|
727
|
+
const names = (rec.tools ?? []).map((raw) => publicToolName(serverName, raw));
|
|
728
|
+
if (names.length) out.set(serverName, names);
|
|
729
|
+
}
|
|
730
|
+
return out;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** 对单个 agent 应用会话级门控:禁用连接器的工具全部 deny。 */
|
|
708
734
|
const applyGate = (agent) => {
|
|
709
735
|
const agentCtx = agent?.ctx;
|
|
710
736
|
if (!agentCtx?.tools?.restrict) return;
|
|
711
737
|
const sessionId = String(agent.session?.id ?? agent.id ?? '');
|
|
712
|
-
try { RESTRICTORS.get(agent.id)?.(); } catch { /* ignore */ }
|
|
713
738
|
const allowed = enabledServersOf(currentConfig(), sessionId);
|
|
739
|
+
const byServer = mountedToolNamesByServer();
|
|
740
|
+
// 还没挂载完(工具尚未注册)时调用 restrict 会因 unknown name 抛错:留到挂载后再试
|
|
741
|
+
const deny = [];
|
|
742
|
+
for (const [serverName, names] of byServer) {
|
|
743
|
+
if (!allowed.has(serverName)) deny.push(...names);
|
|
744
|
+
}
|
|
745
|
+
try { RESTRICTORS.get(agent.id)?.(); } catch { /* ignore */ }
|
|
746
|
+
RESTRICTORS.delete(agent.id);
|
|
747
|
+
if (deny.length === 0) { GATE_PENDING.delete(agent.id); return; }
|
|
714
748
|
try {
|
|
715
|
-
const dispose = agentCtx.tools.restrict(
|
|
716
|
-
const name = typeof tool === 'string' ? tool : tool?.name ?? tool?.definition?.name;
|
|
717
|
-
if (typeof name !== 'string') return true;
|
|
718
|
-
const m = /^mcp__([A-Za-z0-9_-]+)__/.exec(name);
|
|
719
|
-
return m === null || allowed.has(m[1]);
|
|
720
|
-
});
|
|
749
|
+
const dispose = agentCtx.tools.restrict({ deny });
|
|
721
750
|
RESTRICTORS.set(agent.id, dispose);
|
|
751
|
+
GATE_PENDING.delete(agent.id);
|
|
722
752
|
} catch (e) {
|
|
723
|
-
|
|
753
|
+
// 工具名尚未生效(挂载中):记下,稍后重试
|
|
754
|
+
if (/unknown global tool/i.test(String(e?.message ?? ''))) {
|
|
755
|
+
GATE_PENDING.add(agent.id);
|
|
756
|
+
} else {
|
|
757
|
+
warn(`会话 ${sessionId} 工具门控失败:`, e?.message ?? e);
|
|
758
|
+
}
|
|
724
759
|
}
|
|
725
760
|
};
|
|
726
761
|
|
|
762
|
+
/** 挂载/卸载后重新应用所有 agent 的门控(门控依赖已注册的工具名)。 */
|
|
763
|
+
const reapplyGates = () => {
|
|
764
|
+
for (const agent of AGENTS?.list?.() ?? []) applyGate(agent);
|
|
765
|
+
};
|
|
766
|
+
|
|
727
767
|
/* ---------------- HTTP 路由 ---------------- */
|
|
728
768
|
|
|
729
769
|
const state = () => {
|
|
@@ -915,7 +955,7 @@ export async function apply(ctx, config = {}) {
|
|
|
915
955
|
'POST /wx-auth/start': async (body) => {
|
|
916
956
|
const serverName = String(body?.serverName ?? 'weixin-mcp');
|
|
917
957
|
const WX_BASE = 'https://ilinkai.weixin.qq.com';
|
|
918
|
-
const ACCOUNTS_DIR = `${
|
|
958
|
+
const ACCOUNTS_DIR = `${homedir()}/.weixin-mcp/accounts`;
|
|
919
959
|
const genQr = async (url) => {
|
|
920
960
|
const { default: QR } = await import('qrcode');
|
|
921
961
|
return QR.toDataURL(url, { width: 360, margin: 2 });
|