// dsh-plugin-mcn MCP stdio 客户端(复用已验证方案;拆分自 index.js) import { homedir } from "node:os"; import { join, isAbsolute, resolve } from "node:path"; import { spawn } from "node:child_process"; import { NPX_CMD, MCP_PACKAGE, MCP_ENV } from "./config.js"; function dshHome() { const configured = process.env.DSH_HOME?.trim(); if (!configured) return join(homedir(), ".dsh"); return isAbsolute(configured) ? configured : resolve(configured); } function callMcpTool(toolName, args = {}) { return new Promise((resolvePromise, reject) => { const reqs = [ JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "dsh-plugin-mcn", version: "0.1.0" } } }), JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }), JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: toolName, arguments: args } }), ].join("\n") + "\n"; const child = spawn(NPX_CMD, ["-y", MCP_PACKAGE], { env: { ...process.env, ...MCP_ENV }, shell: true, stdio: ["pipe", "pipe", "inherit"], }); let stdout = ""; let settled = false; const timer = setTimeout(() => { if (!settled) { settled = true; child.kill(); reject(new Error("MCP 调用超时")); } }, 120000); child.stdout.on("data", (chunk) => { stdout += chunk.toString("utf8"); }); child.on("close", (code) => { if (settled) return; settled = true; clearTimeout(timer); try { // 找到 id=2 的响应 const lines = stdout.split("\n"); for (const line of lines) { if (!line.includes('"id":2')) continue; const msg = JSON.parse(line); if (msg.result?.isError) { reject(new Error(msg.result.content?.[0]?.text || "MCP 工具报错")); return; } // 优先 structuredContent(结构化数据);无则回退 text(格式化文本) const sc = msg.result?.structuredContent; const text = msg.result?.content?.[0]?.text ?? ""; resolvePromise({ structured: sc ?? null, text }); return; } reject(new Error("未找到 MCP 工具响应")); } catch (e) { reject(new Error("解析 MCP 响应失败: " + e.message)); } }); child.stdin.write(reqs); child.stdin.end(); child.on("error", (e) => { if (!settled) { settled = true; clearTimeout(timer); reject(new Error("MCP 进程启动失败: " + e.message)); } }); }); } export { dshHome, callMcpTool };