openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
112 lines (111 loc) • 5.32 kB
JavaScript
import { n as resolveOpenClawPackageRootSync } from "./openclaw-root-CfYY-fyD.js";
import { createRequire } from "node:module";
import fs from "node:fs";
import path from "node:path";
//#region src/mcp/openclaw-tools-serve-config.ts
/**
* Shared contract between the openclaw-tools MCP stdio entry and the callers
* that inject it into CLI harness runs. Keep this module free of MCP SDK and
* tool-runtime imports so CLI-runner prepare paths can build server configs
* without loading the server.
*/
const OPENCLAW_TOOLS_MCP_TOOLS_ENV = "OPENCLAW_TOOLS_MCP_TOOLS";
const OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_SURFACE_ENV = "OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_SURFACE";
const OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_APPROVAL_ARMED_ENV = "OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_APPROVAL_ARMED";
const OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_PROPOSAL_ENV = "OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_PROPOSAL";
const APPROVAL_ARMED_OPERATOR_ONLY_VALUE = "operator-only";
const OPENCLAW_TOOLS_MCP_TOOL_IDS = ["cron", "openclaw"];
function isOpenClawToolsMcpToolId(value) {
return OPENCLAW_TOOLS_MCP_TOOL_IDS.includes(value);
}
/** Parse the served tool selection; the default stays cron for acpx bridges. */
function resolveOpenClawToolsMcpToolSelection(env = process.env) {
const raw = env[OPENCLAW_TOOLS_MCP_TOOLS_ENV]?.trim();
if (!raw) return ["cron"];
const entries = raw.split(",").map((entry) => entry.trim()).filter(Boolean);
const selection = entries.filter(isOpenClawToolsMcpToolId);
if (selection.length === 0 || selection.length !== entries.length) throw new Error(`${OPENCLAW_TOOLS_MCP_TOOLS_ENV} must be a comma list of: ${OPENCLAW_TOOLS_MCP_TOOL_IDS.join(", ")}`);
return selection;
}
/** Parse the OpenClaw surface for served openclaw tools; defaults to cli. */
function resolveOpenClawToolsMcpSystemAgentSurface(env = process.env) {
const raw = env[OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_SURFACE_ENV]?.trim();
if (!raw || raw === "cli") return "cli";
if (raw === "gateway") return "gateway";
throw new Error(`${OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_SURFACE_ENV} must be "cli" or "gateway"`);
}
/**
* Reconstruct per-turn approval state for the served openclaw tool. The
* stdio server runs out of process, so the host passes the armed bit and the
* pending proposal hash through env; the host mirrors transitions back from
* tool events (see mirrorSystemAgentToolStateFromEvents in agent-turn.ts).
*/
function resolveOpenClawToolsMcpSystemAgentApproval(env = process.env) {
const pendingProposal = env[OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_PROPOSAL_ENV]?.trim();
const armedValue = env[OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_APPROVAL_ARMED_ENV]?.trim();
return {
approvalArmed: armedValue === "1",
proposalRef: pendingProposal ? { current: pendingProposal } : {},
...armedValue === APPROVAL_ARMED_OPERATOR_ONLY_VALUE ? { operatorApprovalOnly: true } : {}
};
}
function resolveTsxImportSpecifier() {
try {
return createRequire(import.meta.url).resolve("tsx");
} catch {
return "tsx";
}
}
function resolveOpenClawToolsServeCommand() {
const packageRoot = resolveOpenClawPackageRootSync({
argv1: process.argv[1],
moduleUrl: import.meta.url,
cwd: process.cwd()
});
if (!packageRoot) throw new Error("openclaw-tools MCP: could not resolve the OpenClaw package root");
const distEntry = path.join(packageRoot, "dist", "mcp", "openclaw-tools-serve.js");
if (fs.existsSync(distEntry)) return {
command: process.execPath,
args: [distEntry]
};
const sourceEntry = path.join(packageRoot, "src", "mcp", "openclaw-tools-serve.ts");
if (!fs.existsSync(sourceEntry)) throw new Error(`openclaw-tools MCP: no serve entry under ${packageRoot}`);
if (process.versions.bun) return {
command: process.execPath,
args: [sourceEntry]
};
return {
command: process.execPath,
args: [
"--import",
resolveTsxImportSpecifier(),
sourceEntry
]
};
}
/**
* OpenClaw CLI-harness runs get exactly one MCP server: this stdio entry
* serving the ring-zero openclaw tool. The server keeps the "openclaw" name
* so backend tool pre-approvals (e.g. Claude's --allowedTools mcp__openclaw__*)
* apply without per-backend argument surgery.
*/
function buildSystemAgentToolsMcpServerConfig(options) {
const entry = resolveOpenClawToolsServeCommand();
const pendingProposal = options.proposalRef?.current;
return { mcpServers: { openclaw: {
command: entry.command,
args: options.agentId ? [
...entry.args,
"--openclaw-agent-id",
options.agentId
] : entry.args,
env: {
[OPENCLAW_TOOLS_MCP_TOOLS_ENV]: "openclaw",
[OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_SURFACE_ENV]: options.surface,
...options.operatorApprovalOnly === true ? { [OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_APPROVAL_ARMED_ENV]: APPROVAL_ARMED_OPERATOR_ONLY_VALUE } : options.approvalArmed === true ? { [OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_APPROVAL_ARMED_ENV]: "1" } : {},
...pendingProposal ? { [OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_PROPOSAL_ENV]: pendingProposal } : {}
}
} } };
}
//#endregion
export { buildSystemAgentToolsMcpServerConfig as a, resolveOpenClawToolsMcpToolSelection as c, OPENCLAW_TOOLS_MCP_TOOLS_ENV as i, OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_PROPOSAL_ENV as n, resolveOpenClawToolsMcpSystemAgentApproval as o, OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_SURFACE_ENV as r, resolveOpenClawToolsMcpSystemAgentSurface as s, OPENCLAW_TOOLS_MCP_SYSTEM_AGENT_APPROVAL_ARMED_ENV as t };