UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

206 lines (205 loc) 10.7 kB
import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js"; import { p as normalizeTrimmedStringList } from "./string-normalization-DsCfAx8q.js"; import "./utils-P__uGsPB.js"; import { r as normalizeConfiguredMcpServers } from "./mcp-config-normalize-CWrDNtye.js"; import { i as loadEnabledBundleMcpConfig } from "./bundle-mcp-zo_t2klC.js"; import { n as prepareOwnedBundleMcpDataDirs } from "./bundle-mcp-config-D7dA4odX.js"; import { i as partitionMcpServersByConnectionScope } from "./mcp-connection-resolver-FyG5JEPz.js"; import { i as resolveProjectedMcpCodexToolApprovalMode } from "./mcp-codex-tool-approval-UMdM31mU.js"; import { a as shouldCreateBundleMcpRuntimeForAttempt } from "./attempt-tool-construction-plan-CRC43c_k.js"; import { t as loadMcpToolGrants } from "./exec-approvals-mcp-JYee-ZtI.js"; import crypto from "node:crypto"; //#region src/agents/bundle-mcp-adapter.ts function normalizeMcpStringRecord(value) { if (!isRecord(value)) return; const entries = Object.entries(value).filter((entry) => { return typeof entry[1] === "string"; }); return entries.length > 0 ? Object.fromEntries(entries) : void 0; } function decodeHeaderEnvPlaceholder(value) { const match = /^(Bearer )?\${([A-Z0-9_]+)}$/.exec(value); return match?.[2] ? { envVar: match[2], bearer: Boolean(match[1]) } : null; } const COMMON_STRING_FIELDS = [ "command", "cwd", "url" ]; function normalizeBundleMcpServerConfig(server, fields = {}) { const next = {}; for (const field of [...COMMON_STRING_FIELDS, ...fields.strings ?? []]) if (typeof server[field] === "string") next[field] = server[field]; for (const field of fields.booleans ?? []) if (typeof server[field] === "boolean") next[field] = server[field]; const args = Array.isArray(server.args) && server.args.every((entry) => typeof entry === "string") ? [...server.args] : void 0; if (args) next.args = args; const env = normalizeMcpStringRecord(server.env); if (env) next.env = env; return next; } //#endregion //#region src/agents/codex-mcp-config.ts /** * Projects enabled bundle MCP servers into Codex app-server thread config. * The projection keeps loopback approval defaults and header env placeholders * compatible with Codex's MCP config shape. */ function assertCodexExactToolFilters(serverName, fieldName, patterns) { const wildcard = patterns.find((pattern) => pattern.includes("*")); if (!wildcard) return; throw new Error(`Cannot project mcp.servers.${serverName}.toolFilter.${fieldName} pattern "${wildcard}" into Codex ${fieldName === "include" ? "enabled_tools" : "disabled_tools"}: Codex MCP projection only supports exact tool names.`); } function applyCodexToolFilter(next, name, server) { if (!isRecord(server.toolFilter)) return; const include = normalizeTrimmedStringList(server.toolFilter.include); const exclude = normalizeTrimmedStringList(server.toolFilter.exclude); assertCodexExactToolFilters(name, "include", include); assertCodexExactToolFilters(name, "exclude", exclude); if (include.length > 0) next.enabled_tools = include; if (exclude.length > 0) next.disabled_tools = exclude; } /** Adds exact session denials to a server's configured filter before Codex projection. */ function applyCodexSessionMcpToolDenials(name, server, toolOverrides) { const denialMap = toolOverrides?.mcpToolsDeny; const denied = denialMap && Object.hasOwn(denialMap, name) ? denialMap[name] : void 0; if (!denied?.length) return server; const toolFilter = isRecord(server.toolFilter) ? server.toolFilter : {}; const existing = normalizeTrimmedStringList(toolFilter.exclude); return { ...server, toolFilter: { ...toolFilter, exclude: [.../* @__PURE__ */ new Set([...existing, ...denied])].toSorted() } }; } /** Normalizes one bundle MCP server into Codex's mcp_servers shape. */ function normalizeCodexMcpServerConfig(name, server, grants = []) { const next = normalizeBundleMcpServerConfig(server); applyCodexToolFilter(next, name, server); const defaultToolsApprovalMode = resolveProjectedMcpCodexToolApprovalMode(name, server); if (defaultToolsApprovalMode) next.default_tools_approval_mode = defaultToolsApprovalMode; if (defaultToolsApprovalMode === void 0 || defaultToolsApprovalMode === "auto") { const tools = grants.filter((grant) => grant.server === name).map((grant) => [grant.tool, { approval_mode: "approve" }]).toSorted(([left], [right]) => left.localeCompare(right)); if (tools.length > 0) next.tools = Object.fromEntries(tools); } const httpHeaders = normalizeMcpStringRecord(server.headers); if (httpHeaders) { const staticHeaders = {}; const envHeaders = {}; for (const [nameLocal, value] of Object.entries(httpHeaders)) { const decoded = decodeHeaderEnvPlaceholder(value); if (!decoded) { staticHeaders[nameLocal] = value; continue; } if (decoded.bearer && normalizeOptionalLowercaseString(nameLocal) === "authorization") { next.bearer_token_env_var = decoded.envVar; continue; } envHeaders[nameLocal] = decoded.envVar; } if (Object.keys(staticHeaders).length > 0) next.http_headers = staticHeaders; if (Object.keys(envHeaders).length > 0) next.env_http_headers = envHeaders; } return next; } /** * Build Codex `mcp_servers` config from normalized bundle MCP config. * Requester-scoped servers are excluded: harness-native MCP clients are * session-shared and must never dial placeholder or requester-bound URLs. */ function buildCodexMcpServersConfig(config, grants = []) { const { staticServers } = partitionMcpServersByConnectionScope(config.mcpServers); return Object.fromEntries(Object.entries(staticServers).map(([name, server]) => [name, normalizeCodexMcpServerConfig(name, server, grants)])); } /** Side questions need bundle policy without provisioning transports or plugin data directories. */ function loadCodexBundleMcpApprovalConfig(params) { const { config } = loadEnabledBundleMcpConfig(params); const configuredMcp = normalizeConfiguredMcpServers(params.cfg?.mcp?.servers); const selected = selectCodexBundleMcpConfig({ mcpServers: { ...config.mcpServers, ...configuredMcp } }, configuredMcp, params.toolOverrides); const { staticServers } = partitionMcpServersByConnectionScope(selected.mcpServers); return Object.fromEntries(Object.keys(staticServers).map((name) => [name, { default_tools_approval_mode: resolveProjectedMcpCodexToolApprovalMode(name, configuredMcp[name] ?? {}) ?? resolveProjectedMcpCodexToolApprovalMode(name, config.mcpServers[name] ?? {}) }])); } function selectCodexBundleMcpConfig(config, configuredMcp, toolOverrides) { const serverOverrides = toolOverrides?.mcpServers; return { mcpServers: Object.fromEntries(Object.entries(config.mcpServers).filter(([name]) => { const override = serverOverrides && Object.hasOwn(serverOverrides, name) ? serverOverrides[name] : void 0; return override !== false && (override === true || configuredMcp[name]?.enabled !== false); }).map(([name, server]) => [name, applyCodexSessionMcpToolDenials(name, server, toolOverrides)])) }; } function stableJsonValue(value) { if (Array.isArray(value)) return value.map(stableJsonValue); if (!value || typeof value !== "object") return value; return Object.fromEntries(Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, stableJsonValue(child)])); } function fingerprintCodexMcpServersConfig(config) { return crypto.createHash("sha256").update(JSON.stringify(stableJsonValue(config))).digest("hex"); } /** Load bundle MCP config for one Codex app-server thread. */ function loadCodexBundleMcpThreadConfigCore(params) { if (!shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: params.toolsEnabled ?? true, disableTools: params.disableTools, toolsAllow: params.toolsAllow })) return { diagnostics: [], evaluated: true, staticServerNames: [], userStaticServerNames: [] }; const bundleMcp = loadEnabledBundleMcpConfig({ workspaceDir: params.workspaceDir, cfg: params.cfg, manifestRegistry: params.manifestRegistry }); const configuredMcp = normalizeConfiguredMcpServers(params.cfg?.mcp?.servers); const serverOverrides = params.toolOverrides?.mcpServers; const effectiveConfig = selectCodexBundleMcpConfig(bundleMcp.config, configuredMcp, params.toolOverrides); const enabledConfiguredMcp = Object.fromEntries(Object.entries(configuredMcp).filter(([name, server]) => { const override = serverOverrides && Object.hasOwn(serverOverrides, name) ? serverOverrides[name] : void 0; return override !== false && (override === true || server.enabled !== false); })); const { staticServers: configuredStaticServers } = partitionMcpServersByConnectionScope({ ...effectiveConfig.mcpServers, ...enabledConfiguredMcp }); const { staticServers: userStaticServers } = partitionMcpServersByConnectionScope(enabledConfiguredMcp); const staticServerNames = Object.keys(configuredStaticServers).toSorted((left, right) => left.localeCompare(right)); const userStaticServerNames = Object.keys(userStaticServers).toSorted((left, right) => left.localeCompare(right)); if (params.preparationOnly && Object.keys(bundleMcp.prepareDataDirsByServer ?? {}).length) throw new Error("Native fork preparation cannot provision plugin data directories. Complete plugin setup before retrying."); const preparedDataDirs = prepareOwnedBundleMcpDataDirs({ config: effectiveConfig, prepareDataDirsByServer: bundleMcp.prepareDataDirsByServer ?? {} }); const diagnostics = [...bundleMcp.diagnostics, ...preparedDataDirs.diagnostics]; const configuredGrants = (params.agentId ? loadMcpToolGrants(params.agentId) : []).filter((grant) => { const server = Object.hasOwn(configuredMcp, grant.server) ? configuredMcp[grant.server] : void 0; if (!server) return false; const mode = resolveProjectedMcpCodexToolApprovalMode(grant.server, server); return mode === void 0 || mode === "auto"; }); const mcpServers = buildCodexMcpServersConfig(preparedDataDirs.config, configuredGrants); if (Object.keys(mcpServers).length === 0) return { diagnostics, evaluated: true, staticServerNames, userStaticServerNames }; return { configPatch: { mcp_servers: mcpServers }, diagnostics, evaluated: true, fingerprint: fingerprintCodexMcpServersConfig(mcpServers), staticServerNames, userStaticServerNames }; } //#endregion export { normalizeCodexMcpServerConfig as a, normalizeMcpStringRecord as c, loadCodexBundleMcpThreadConfigCore as i, buildCodexMcpServersConfig as n, decodeHeaderEnvPlaceholder as o, loadCodexBundleMcpApprovalConfig as r, normalizeBundleMcpServerConfig as s, applyCodexSessionMcpToolDenials as t };