openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
412 lines (411 loc) • 15.6 kB
JavaScript
import { c as isRecord, l as isStringRecord } from "./record-coerce-DItp3I4t.js";
import { r as isPathInside } from "./path-guards-Cp-mGr3-.js";
import "./utils-P__uGsPB.js";
import { a as pluginCacheRealpathSync, r as pluginCacheExistsSync } from "./plugin-cache-files-DLPF_Tw2.js";
import { n as resolveActivePluginInstallRoots } from "./install-root-context-BSQp2WLR.js";
import { c as normalizeBundlePathList, i as CURSOR_BUNDLE_MANIFEST_RELATIVE_PATH, n as CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH, r as CODEX_BUNDLE_MANIFEST_RELATIVE_PATH, s as mergeBundlePathLists, t as AGENT_BUNDLE_MANIFEST_RELATIVE_PATH } from "./bundle-manifest-C0Vv9-Hj.js";
import { t as encodePluginInstallDirName } from "./install-paths-ChnxikBv.js";
import { t as applyMergePatch } from "./merge-patch-DCMj2JVh.js";
import { n as resolveMcpTransportConfig } from "./mcp-transport-config-mph-T0Ys.js";
import { i as resolveBundleJsonOpenFailure, n as loadEnabledBundleConfig, r as readBundleJsonObject } from "./bundle-config-shared-BUe11hAy.js";
import path from "node:path";
//#region src/plugins/bundle-mcp.ts
const MANIFEST_PATH_BY_FORMAT = {
agent: AGENT_BUNDLE_MANIFEST_RELATIVE_PATH,
claude: CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH,
codex: CODEX_BUNDLE_MANIFEST_RELATIVE_PATH,
cursor: CURSOR_BUNDLE_MANIFEST_RELATIVE_PATH
};
const CLAUDE_PLUGIN_ROOT_PLACEHOLDER = "${CLAUDE_PLUGIN_ROOT}";
const AGENT_PLUGIN_ROOT_PLACEHOLDER = "${PLUGIN_ROOT}";
const AGENT_PLUGIN_DATA_PLACEHOLDER = "${PLUGIN_DATA}";
const AGENT_MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
const BUNDLE_PLACEHOLDER_PATTERN = /\$\{(?:CLAUDE_PLUGIN_ROOT|PLUGIN_ROOT|PLUGIN_DATA)\}/g;
const AGENT_MCP_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["$schema", "mcpServers"]);
const AGENT_STDIO_KEYS = /* @__PURE__ */ new Set([
"type",
"command",
"args",
"env",
"cwd"
]);
const AGENT_HTTP_KEYS = /* @__PURE__ */ new Set([
"type",
"url",
"headers"
]);
function resolveBundleMcpConfigPaths(params) {
if (params.bundleFormat === "agent") return pluginCacheExistsSync(path.join(params.rootDir, "mcp.json")) ? ["mcp.json"] : [];
const declared = normalizeBundlePathList(params.raw.mcpServers);
const defaults = pluginCacheExistsSync(path.join(params.rootDir, ".mcp.json")) ? [".mcp.json"] : [];
return mergeBundlePathLists(defaults, declared);
}
function extractMcpServerMap(raw) {
if (!isRecord(raw)) return {};
const nested = isRecord(raw.mcpServers) ? raw.mcpServers : isRecord(raw.servers) ? raw.servers : raw;
if (!isRecord(nested)) return {};
const result = {};
for (const [serverName, serverRaw] of Object.entries(nested)) {
if (!isRecord(serverRaw)) continue;
result[serverName] = { ...serverRaw };
}
return result;
}
function isExplicitRelativePath(value) {
return value === "." || value === ".." || value.startsWith("./") || value.startsWith("../");
}
function expandBundleRootPlaceholders(params) {
return params.value.replace(BUNDLE_PLACEHOLDER_PATTERN, (placeholder) => {
if (placeholder === CLAUDE_PLUGIN_ROOT_PLACEHOLDER || placeholder === AGENT_PLUGIN_ROOT_PLACEHOLDER && params.pluginDataDir) return params.rootDir;
return params.pluginDataDir ?? placeholder;
});
}
function normalizeBundlePath(targetPath) {
return path.normalize(path.resolve(targetPath));
}
function normalizeExpandedAbsolutePath(value) {
return path.isAbsolute(value) ? path.normalize(value) : value;
}
function absolutizeBundleMcpServer(params) {
const next = { ...params.server };
if (typeof next.cwd !== "string" && typeof next.workingDirectory !== "string" && (!params.agentFormat || typeof next.command === "string")) next.cwd = params.baseDir;
const command = next.command;
if (typeof command === "string") {
const expanded = expandBundleRootPlaceholders({
value: command,
rootDir: params.rootDir,
pluginDataDir: params.pluginDataDir
});
next.command = isExplicitRelativePath(expanded) ? path.resolve(params.baseDir, expanded) : normalizeExpandedAbsolutePath(expanded);
}
const cwd = next.cwd;
if (typeof cwd === "string") {
const expanded = expandBundleRootPlaceholders({
value: cwd,
rootDir: params.rootDir,
pluginDataDir: params.pluginDataDir
});
next.cwd = path.isAbsolute(expanded) ? expanded : path.resolve(params.baseDir, expanded);
}
const workingDirectory = next.workingDirectory;
if (typeof workingDirectory === "string") {
const expanded = expandBundleRootPlaceholders({
value: workingDirectory,
rootDir: params.rootDir,
pluginDataDir: params.pluginDataDir
});
next.workingDirectory = path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(params.baseDir, expanded);
}
if (Array.isArray(next.args)) next.args = next.args.map((entry) => {
if (typeof entry !== "string") return entry;
const expanded = expandBundleRootPlaceholders({
value: entry,
rootDir: params.rootDir,
pluginDataDir: params.pluginDataDir
});
if (!isExplicitRelativePath(expanded)) return normalizeExpandedAbsolutePath(expanded);
return path.resolve(params.baseDir, expanded);
});
if (isRecord(next.env)) next.env = Object.fromEntries(Object.entries(next.env).map(([key, value]) => [key, typeof value === "string" ? normalizeExpandedAbsolutePath(expandBundleRootPlaceholders({
value,
rootDir: params.rootDir,
pluginDataDir: params.pluginDataDir
})) : value]));
if (params.pluginDataDir && typeof next.command === "string") next.env = {
...isRecord(next.env) ? next.env : {},
PLUGIN_ROOT: params.rootDir,
PLUGIN_DATA: params.pluginDataDir
};
return next;
}
function hasOnlyKeys(raw, allowed) {
return Object.keys(raw).every((key) => allowed.has(key));
}
function isValidAgentCommand(command, rootDir) {
if (typeof command !== "string" || command.length === 0) return false;
if (command.startsWith("./")) return command.length > 2 && isPathInside(rootDir, path.resolve(rootDir, command));
return !/[\s/\\]/.test(command);
}
function isValidAgentCwd(cwd, rootDir, pluginDataDir) {
if (typeof cwd !== "string") return false;
let baseDir;
if (cwd.startsWith("./")) baseDir = rootDir;
else if (cwd === AGENT_PLUGIN_ROOT_PLACEHOLDER || cwd.startsWith(`${AGENT_PLUGIN_ROOT_PLACEHOLDER}/`)) baseDir = rootDir;
else if (cwd === AGENT_PLUGIN_DATA_PLACEHOLDER || cwd.startsWith(`${AGENT_PLUGIN_DATA_PLACEHOLDER}/`)) baseDir = pluginDataDir;
else return false;
const expanded = expandBundleRootPlaceholders({
value: cwd,
rootDir,
pluginDataDir
});
return isPathInside(baseDir, path.resolve(baseDir, expanded));
}
function validateAgentMcpServer(params) {
if (!isRecord(params.raw) || typeof params.raw.type !== "string") return {
ok: false,
error: "configuration must be an object with a supported type"
};
const type = params.raw.type;
if (type === "stdio") {
if (!hasOnlyKeys(params.raw, AGENT_STDIO_KEYS)) return {
ok: false,
error: "stdio configuration contains unknown fields"
};
if (!isValidAgentCommand(params.raw.command, params.rootDir)) return {
ok: false,
error: "stdio command must be a bare name or ./-relative path"
};
if (params.raw.args !== void 0 && (!Array.isArray(params.raw.args) || !params.raw.args.every((entry) => typeof entry === "string"))) return {
ok: false,
error: "stdio args must be an array of strings"
};
if (params.raw.env !== void 0 && !isStringRecord(params.raw.env)) return {
ok: false,
error: "stdio env must contain only string values"
};
if (isRecord(params.raw.env) && (Object.hasOwn(params.raw.env, "PLUGIN_ROOT") || Object.hasOwn(params.raw.env, "PLUGIN_DATA"))) return {
ok: false,
error: "stdio env must not define PLUGIN_ROOT or PLUGIN_DATA"
};
if (params.raw.cwd !== void 0 && !isValidAgentCwd(params.raw.cwd, params.rootDir, params.pluginDataDir)) return {
ok: false,
error: "stdio cwd must remain within PLUGIN_ROOT or PLUGIN_DATA"
};
} else if (type === "streamable-http" || type === "sse") {
if (!hasOnlyKeys(params.raw, AGENT_HTTP_KEYS)) return {
ok: false,
error: `${type} configuration contains unknown fields`
};
if (typeof params.raw.url !== "string" || params.raw.url.length === 0) return {
ok: false,
error: `${type} url must be a non-empty string`
};
if (params.raw.headers !== void 0 && !isStringRecord(params.raw.headers)) return {
ok: false,
error: `${type} headers must contain only string values`
};
} else return {
ok: false,
error: `unsupported type: ${type}`
};
const server = {
...params.raw,
transport: type
};
delete server.type;
return {
ok: true,
server
};
}
function resolveAgentPluginDataDir(pluginId) {
return path.join(resolveActivePluginInstallRoots().stateDir, "plugin-data", encodePluginInstallDirName(pluginId));
}
function extractAgentMcpServerMap(params) {
if (params.raw.$schema !== AGENT_MCP_SCHEMA || !hasOnlyKeys(params.raw, AGENT_MCP_TOP_LEVEL_KEYS) || !isRecord(params.raw.mcpServers)) return {
servers: {},
diagnostics: [`invalid mcp.json: expected only $schema=${AGENT_MCP_SCHEMA} and object mcpServers`]
};
const pluginDataDir = resolveAgentPluginDataDir(params.pluginId);
const servers = {};
const diagnostics = [];
for (const [serverName, raw] of Object.entries(params.raw.mcpServers)) {
const validated = validateAgentMcpServer({
raw,
rootDir: params.rootDir,
pluginDataDir
});
if (!validated.ok) {
diagnostics.push(`invalid MCP server "${serverName}" in mcp.json: ${validated.error}`);
continue;
}
servers[serverName] = validated.server;
}
if (!Object.values(servers).some((server) => server.transport === "stdio")) return {
servers,
diagnostics
};
return {
servers,
diagnostics,
pluginDataDir
};
}
function loadBundleFileBackedMcpConfig(params) {
const rootDir = params.bundleFormat === "agent" ? pluginCacheRealpathSync(params.rootDir) : normalizeBundlePath(params.rootDir);
const absolutePath = path.resolve(rootDir, params.relativePath);
const result = readBundleJsonObject({
rootDir,
relativePath: params.relativePath,
onOpenFailure: (failure) => resolveBundleJsonOpenFailure({
failure,
relativePath: params.relativePath,
allowMissing: params.bundleFormat !== "agent"
})
});
if (!result.ok) return {
config: {
mcpServers: {},
prepareDataDirsByServer: {}
},
diagnostics: [result.reason === "open" ? result.error : `unable to read ${params.relativePath}: ${result.error}`]
};
const agentLoaded = params.bundleFormat === "agent" ? extractAgentMcpServerMap({
raw: result.raw,
pluginId: params.pluginId,
rootDir
}) : void 0;
const servers = agentLoaded?.servers ?? extractMcpServerMap(result.raw);
const baseDir = normalizeBundlePath(path.dirname(absolutePath));
return {
config: {
mcpServers: Object.fromEntries(Object.entries(servers).map(([serverName, server]) => [serverName, absolutizeBundleMcpServer({
rootDir,
baseDir,
server,
pluginDataDir: agentLoaded?.pluginDataDir,
agentFormat: params.bundleFormat === "agent"
})])),
prepareDataDirsByServer: Object.fromEntries(Object.entries(servers).map(([serverName, server]) => [serverName, agentLoaded?.pluginDataDir && server.transport === "stdio" ? {
pluginId: params.pluginId,
dataDir: agentLoaded.pluginDataDir
} : null]))
},
diagnostics: agentLoaded?.diagnostics ?? []
};
}
function loadBundleInlineMcpConfig(params) {
if (!isRecord(params.raw.mcpServers)) return {
mcpServers: {},
prepareDataDirsByServer: {}
};
const baseDir = normalizeBundlePath(params.baseDir);
const servers = extractMcpServerMap(params.raw.mcpServers);
return {
mcpServers: Object.fromEntries(Object.entries(servers).map(([serverName, server]) => [serverName, absolutizeBundleMcpServer({
rootDir: baseDir,
baseDir,
server
})])),
prepareDataDirsByServer: Object.fromEntries(Object.keys(servers).map((serverName) => [serverName, null]))
};
}
function loadNativePluginMcpConfig(params) {
const rootDir = normalizeBundlePath(params.rootDir);
return {
config: {
mcpServers: Object.fromEntries(Object.entries(params.mcpServers).map(([serverName, server]) => [serverName, absolutizeBundleMcpServer({
rootDir,
baseDir: rootDir,
server
})])),
prepareDataDirsByServer: Object.fromEntries(Object.keys(params.mcpServers).map((serverName) => [serverName, null]))
},
diagnostics: []
};
}
function loadBundleMcpConfig(params) {
const manifestRelativePath = MANIFEST_PATH_BY_FORMAT[params.bundleFormat];
const manifestLoaded = readBundleJsonObject({
rootDir: params.rootDir,
relativePath: manifestRelativePath,
onOpenFailure: (failure) => resolveBundleJsonOpenFailure({
failure,
relativePath: manifestRelativePath,
allowMissing: params.bundleFormat === "claude"
})
});
if (!manifestLoaded.ok) return {
config: {
mcpServers: {},
prepareDataDirsByServer: {}
},
diagnostics: [manifestLoaded.error]
};
let merged = {
mcpServers: {},
prepareDataDirsByServer: {}
};
const filePaths = resolveBundleMcpConfigPaths({
raw: manifestLoaded.raw,
rootDir: params.rootDir,
bundleFormat: params.bundleFormat
});
const diagnostics = [];
for (const relativePath of filePaths) {
const loaded = loadBundleFileBackedMcpConfig({
pluginId: params.pluginId,
rootDir: params.rootDir,
relativePath,
bundleFormat: params.bundleFormat
});
diagnostics.push(...loaded.diagnostics);
merged = applyMergePatch(merged, loaded.config);
}
if (params.bundleFormat !== "agent") merged = applyMergePatch(merged, loadBundleInlineMcpConfig({
raw: manifestLoaded.raw,
baseDir: params.rootDir
}));
return {
config: merged,
diagnostics
};
}
function inspectBundleMcpRuntimeSupport(params) {
return inspectMcpServerRuntimeSupport(loadBundleMcpConfig(params));
}
function inspectNativePluginMcpRuntimeSupport(params) {
return inspectMcpServerRuntimeSupport(loadNativePluginMcpConfig(params));
}
function inspectMcpServerRuntimeSupport(loaded) {
const supportedServerNames = [];
const stdioServerNames = [];
const unsupportedServerNames = [];
for (const [serverName, server] of Object.entries(loaded.config.mcpServers)) {
const transport = resolveMcpTransportConfig(serverName, server, { logWarnings: false });
if (transport?.kind === "stdio") {
supportedServerNames.push(serverName);
stdioServerNames.push(serverName);
continue;
}
if (transport?.kind === "http") {
supportedServerNames.push(serverName);
continue;
}
unsupportedServerNames.push(serverName);
}
return {
hasSupportedStdioServer: stdioServerNames.length > 0,
supportedServerNames,
stdioServerNames,
unsupportedServerNames,
diagnostics: loaded.diagnostics
};
}
function loadEnabledBundleMcpConfig(params) {
const loaded = loadEnabledBundleConfig({
workspaceDir: params.workspaceDir,
cfg: params.cfg,
manifestRegistry: params.manifestRegistry,
createEmptyConfig: () => ({
mcpServers: {},
prepareDataDirsByServer: {}
}),
loadBundleConfig: loadBundleMcpConfig,
loadNativePluginConfig: ({ record }) => record.mcpServers ? loadNativePluginMcpConfig({
rootDir: record.rootDir,
mcpServers: record.mcpServers
}) : void 0,
createDiagnostic: (pluginId, message) => ({
pluginId,
message
})
});
return {
config: { mcpServers: loaded.config.mcpServers },
diagnostics: loaded.diagnostics,
prepareDataDirsByServer: Object.fromEntries(Object.entries(loaded.config.prepareDataDirsByServer).filter((entry) => entry[1] !== null))
};
}
//#endregion
export { loadEnabledBundleMcpConfig as i, inspectBundleMcpRuntimeSupport as n, inspectNativePluginMcpRuntimeSupport as r, extractMcpServerMap as t };