mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-11 03:53:01 +00:00
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import { createRequire } from "node:module";
|
|
import path from "node:path";
|
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
import {
|
|
appendQaChildOutputTail,
|
|
createQaChildOutputTail,
|
|
formatQaChildOutputTail,
|
|
} from "./child-output.js";
|
|
import { extractQaToolPayload } from "./extract-tool-payload.js";
|
|
import { resolveQaNodeExecPath } from "./node-exec.js";
|
|
import type {
|
|
QaRuntimeActionHandlerEnv,
|
|
QaSkillStatusEntry,
|
|
QaSuiteRuntimeEnv,
|
|
QaTransportActionName,
|
|
} from "./suite-runtime-types.js";
|
|
|
|
const requireFromHere = createRequire(import.meta.url);
|
|
const MCP_STDERR_TAIL_LIMIT = 8_192;
|
|
const MCP_REQUEST_TIMEOUT_MS = 180_000;
|
|
|
|
function findSkill(skills: QaSkillStatusEntry[], name: string) {
|
|
return skills.find((skill) => skill.name === name);
|
|
}
|
|
|
|
async function writeWorkspaceSkill(params: {
|
|
env: Pick<QaSuiteRuntimeEnv, "gateway">;
|
|
name: string;
|
|
body: string;
|
|
}) {
|
|
const skillDir = path.join(params.env.gateway.workspaceDir, "skills", params.name);
|
|
await fs.mkdir(skillDir, { recursive: true });
|
|
const skillPath = path.join(skillDir, "SKILL.md");
|
|
await fs.writeFile(skillPath, `${params.body.trim()}\n`, "utf8");
|
|
return skillPath;
|
|
}
|
|
|
|
async function callPluginToolsMcp(params: {
|
|
env: Pick<QaSuiteRuntimeEnv, "gateway" | "repoRoot">;
|
|
toolName: string;
|
|
args: Record<string, unknown>;
|
|
}) {
|
|
const transportEnv = Object.fromEntries(
|
|
Object.entries(params.env.gateway.runtimeEnv).filter(
|
|
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
),
|
|
);
|
|
const nodeExecPath = await resolveQaNodeExecPath();
|
|
const transport = new StdioClientTransport({
|
|
command: nodeExecPath,
|
|
args: [
|
|
"--import",
|
|
requireFromHere.resolve("tsx"),
|
|
path.join(params.env.repoRoot, "src/mcp/plugin-tools-serve.ts"),
|
|
],
|
|
stderr: "pipe",
|
|
cwd: params.env.repoRoot,
|
|
env: transportEnv,
|
|
});
|
|
const stderrTail = createQaChildOutputTail(MCP_STDERR_TAIL_LIMIT);
|
|
const stderr = transport.stderr;
|
|
if (stderr && typeof stderr.on === "function") {
|
|
stderr.on("data", (chunk: unknown) => {
|
|
appendQaChildOutputTail(stderrTail, chunk);
|
|
});
|
|
}
|
|
const client = new Client({ name: "openclaw-qa-suite", version: "0.0.0" }, {});
|
|
try {
|
|
await client.connect(transport, { timeout: MCP_REQUEST_TIMEOUT_MS });
|
|
const listed = await client.listTools({}, { timeout: MCP_REQUEST_TIMEOUT_MS });
|
|
const tool = listed.tools.find((entry) => entry.name === params.toolName);
|
|
if (!tool) {
|
|
const availableTools = listed.tools
|
|
.map((entry) => entry.name)
|
|
.filter((name): name is string => typeof name === "string" && name.length > 0)
|
|
.toSorted();
|
|
throw new Error(
|
|
`MCP tool missing: ${params.toolName}; available tools: ${availableTools.join(", ") || "<none>"}`,
|
|
);
|
|
}
|
|
return await client.callTool(
|
|
{
|
|
name: params.toolName,
|
|
arguments: params.args,
|
|
},
|
|
undefined,
|
|
{ timeout: MCP_REQUEST_TIMEOUT_MS },
|
|
);
|
|
} catch (error) {
|
|
const tail = formatQaChildOutputTail(stderrTail, "MCP stderr").trim();
|
|
if (!tail || !(error instanceof Error)) {
|
|
throw error;
|
|
}
|
|
throw new Error(`${error.message}\nMCP stderr tail:\n${tail}`, { cause: error });
|
|
} finally {
|
|
await client.close().catch(() => {});
|
|
}
|
|
}
|
|
|
|
async function handleQaAction(params: {
|
|
env: QaRuntimeActionHandlerEnv;
|
|
action: QaTransportActionName;
|
|
args: Record<string, unknown>;
|
|
}) {
|
|
const result = await params.env.transport.handleAction({
|
|
action: params.action,
|
|
args: params.args,
|
|
cfg: params.env.cfg,
|
|
});
|
|
return extractQaToolPayload(result as Parameters<typeof extractQaToolPayload>[0]);
|
|
}
|
|
|
|
export { callPluginToolsMcp, findSkill, handleQaAction, writeWorkspaceSkill };
|