UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

301 lines (300 loc) 14.5 kB
import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { n as normalizeAgentId, t as isValidAgentId } from "./agent-id-CeT3w4ap.js"; import "./session-key-BnWWjqNc.js"; import { r as normalizeConfiguredMcpServers } from "./mcp-config-normalize-CWrDNtye.js"; import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js"; import { c as releaseSessionMcpRuntime, n as acquireSessionMcpRuntime } from "./agent-bundle-mcp-manager-api-DCHQm4w1.js"; import { i as partitionMcpServersByConnectionScope } from "./mcp-connection-resolver-FyG5JEPz.js"; import { r as resolveMcpBearerBundleConfig, t as requiresMcpBearerProjection } from "./mcp-auth-profile--_BtN_eA.js"; import { r as getPluginToolMeta } from "./tool-metadata-B5aqo73s.js"; import { n as resolveSandboxRuntimeStatus } from "./runtime-status-6c8Jb1Kg.js"; import { t as resolveConversationCapabilityProfile } from "./conversation-capability-profile-B6PkMXFD.js"; import { t as buildBundleMcpToolsFromCatalog } from "./agent-bundle-mcp-materialize-CIHEU_uK.js"; import { t as applyFinalEffectiveToolPolicy } from "./effective-tool-policy-Bco8DCTH.js"; import { t as applyEmbeddedAttemptToolsAllow } from "./attempt-tool-construction-plan-CRC43c_k.js"; import { t as loadMcpToolGrants } from "./exec-approvals-mcp-JYee-ZtI.js"; import { a as normalizeCodexMcpServerConfig, n as buildCodexMcpServersConfig, t as applyCodexSessionMcpToolDenials } from "./codex-mcp-config-DulQr1QH.js"; import { n as serializeTomlInlineValue } from "./toml-inline-C1FYvw5-.js"; //#region src/agents/native-mcp-policy.ts function buildPolicyProjectionTools(catalog) { const callableTools = buildBundleMcpToolsFromCatalog({ catalog }).filter((tool) => getPluginToolMeta(tool)?.mcp?.operation === "tool"); const callableIdentities = new Set(callableTools.flatMap((tool) => { const mcp = getPluginToolMeta(tool)?.mcp; return mcp?.operation === "tool" ? [JSON.stringify([mcp.serverName, mcp.toolName])] : []; })); const hiddenPolicyTools = (catalog.policyTools ?? [...catalog.tools, ...catalog.sessionDeniedTools ?? []]).filter((tool) => !callableIdentities.has(JSON.stringify([tool.serverName, tool.toolName]))); const hiddenTools = buildBundleMcpToolsFromCatalog({ catalog: { ...catalog, tools: hiddenPolicyTools, policyTools: void 0, sessionDeniedTools: void 0 }, reservedToolNames: callableTools.map((tool) => tool.name), includeAppOnlyInventory: true }).filter((tool) => getPluginToolMeta(tool)?.mcp?.operation === "tool"); return [...callableTools, ...hiddenTools]; } async function prepareNativeMcpPolicy(params) { params.runtime.markUsed(); const allTools = buildPolicyProjectionTools(await params.runtime.getCatalog()); const runtimeAllowed = applyEmbeddedAttemptToolsAllow(allTools, params.runtimeToolsAllow, { toolMeta: (tool) => getPluginToolMeta(tool) }); const effectiveAllowed = applyFinalEffectiveToolPolicy({ bundledTools: runtimeAllowed, config: params.config, workspaceDir: params.workspaceDir, conversationCapabilityProfile: params.capabilityProfile, warn: params.warn }); const effectiveAllowedNames = new Set(effectiveAllowed.map((tool) => tool.name)); const servers = {}; for (const tool of allTools) { const mcp = getPluginToolMeta(tool)?.mcp; if (!mcp || mcp.operation !== "tool") continue; const server = servers[mcp.serverName] ??= { serverName: mcp.serverName, safeServerName: mcp.safeServerName, allowedTools: [], deniedTools: [] }; (!mcp.excludedFromOpenClawCatalog && !mcp.deniedBySession && effectiveAllowedNames.has(tool.name) ? server.allowedTools : server.deniedTools).push(mcp.toolName); } for (const server of Object.values(servers)) { server.allowedTools = [...new Set(server.allowedTools)].toSorted(); server.deniedTools = [...new Set(server.deniedTools)].toSorted(); } return { servers: Object.fromEntries(Object.entries(servers).toSorted(([left], [right]) => left.localeCompare(right))) }; } /** Applies one prepared policy to the provider-neutral MCP config shape. */ function applyPreparedNativeMcpPolicy(config, policy) { return { mcpServers: Object.fromEntries(Object.entries(config.mcpServers).flatMap(([serverName, server]) => { const prepared = policy.servers[serverName]; if (!prepared || prepared.allowedTools.length === 0) return []; const toolFilter = isRecord(server.toolFilter) ? server.toolFilter : {}; return [[serverName, { ...server, toolFilter: { ...toolFilter, include: prepared.allowedTools, exclude: prepared.deniedTools } }]]; })) }; } /** Returns raw per-server denials for backends that enforce a deny list. */ function preparedNativeMcpDenials(policy) { const entries = Object.values(policy.servers).filter((server) => server.deniedTools.length > 0).map((server) => [server.serverName, server.deniedTools]); return entries.length > 0 ? Object.fromEntries(entries) : void 0; } //#endregion //#region src/agents/cli-runner/bundle-mcp-codex.ts /** * Codex CLI and app-server bundle MCP projection helpers. */ function normalizeAgentIds(value) { if (!Array.isArray(value)) return []; return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => isValidAgentId(entry)).map((entry) => normalizeAgentId(entry)); } function readCodexProjectionConfig(server) { return isRecord(server.codex) ? server.codex : {}; } function isCodexMcpServerAllowedForAgent(server, options) { const codex = readCodexProjectionConfig(server); if (!Object.hasOwn(codex, "agents")) return true; const agentIds = normalizeAgentIds(codex.agents); if (agentIds.length === 0 || !options?.agentId) return false; return agentIds.includes(normalizeAgentId(options.agentId)); } /** * Applies Codex-only agent scoping before OpenClaw resolves credentials or opens transports. * Session overrides may narrow this result, but cannot widen `codex.agents`. */ function resolveCodexMcpToolOverridesForAgent(cfg, options) { const deniedServerNames = Object.entries(normalizeConfiguredMcpServers(cfg?.mcp?.servers)).filter(([, server]) => !isCodexMcpServerAllowedForAgent(server, options)).map(([name]) => name); if (deniedServerNames.length === 0) return options.toolOverrides; const mcpServers = { ...options.toolOverrides?.mcpServers }; for (const serverName of deniedServerNames) mcpServers[serverName] = false; return { ...options.toolOverrides, mcpServers }; } function readSessionMcpServerOverride(options, name) { const overrides = options?.toolOverrides?.mcpServers; return overrides && Object.hasOwn(overrides, name) ? overrides[name] : void 0; } function selectCodexProjectableMcpServers(cfg, options) { const userServers = normalizeConfiguredMcpServers(cfg?.mcp?.servers); const { staticServers } = partitionMcpServersByConnectionScope(userServers); return Object.fromEntries(Object.entries(staticServers).filter(([serverName, server]) => { const serverOverride = readSessionMcpServerOverride(options, serverName); if (!(serverOverride !== false && (serverOverride === true || server.enabled !== false) && isCodexMcpServerAllowedForAgent(server, options))) return false; if (options?.allowLiteralOAuthProjection === false && requiresMcpBearerProjection(server)) { options.onServerUnavailable?.(serverName, /* @__PURE__ */ new Error(`MCP OAuth bearer projection is only supported for local app-server connections.`)); return false; } return true; })); } /** Returns Codex CLI args with TOML MCP server overrides injected. */ function injectCodexMcpConfigArgs(args, config) { const overrides = serializeTomlInlineValue(buildCodexMcpServersConfig(config)); return [ ...args ?? [], "-c", `mcp_servers=${overrides}` ]; } /** * Codex app-server runtime (extensions/codex) receives its thread config as a * JSON object through JSON-RPC `thread/start`/`thread/resume`, not as `-c` CLI * args. This returns a thread-config patch projecting user-configured * `cfg.mcp.servers` entries into Codex's `mcp_servers` table using the same * per-server normalization the CLI path uses, so app-server agents see the * same user MCP servers the CLI runtime exposes via `injectCodexMcpConfigArgs`. * * Only user-configured servers (`cfg.mcp.servers`) are projected. Plugin- * curated app-server apps are already attached separately through the codex * plugin thread-config `apps` patch, so they must not be re-projected here. */ function buildCodexUserMcpServersThreadConfigPatch(cfg, options) { const entries = Object.entries(selectCodexProjectableMcpServers(cfg, options)); if (entries.length === 0) return; const grants = options?.agentId ? loadMcpToolGrants(options.agentId) : []; const projected = []; for (const [name, server] of entries) projected.push([name, normalizeCodexMcpServerConfig(name, applyCodexSessionMcpToolDenials(name, server, options?.toolOverrides), grants)]); const mcp_servers = Object.fromEntries(projected); if (Object.keys(mcp_servers).length === 0) return; return { mcp_servers }; } /** Async runtime projection that resolves OpenClaw-managed MCP bearer tokens. */ async function buildCodexUserMcpServersThreadConfigPatchForRuntime(cfg, options) { let allowedServers = selectCodexProjectableMcpServers(cfg, options); if (options?.preparationOnly && Object.values(allowedServers).some(requiresMcpBearerProjection)) throw new Error("Native fork preparation cannot resolve MCP bearer credentials. Fork an original imported message instead."); if (options?.preparedNativeMcpPolicy) allowedServers = applyPreparedNativeMcpPolicy({ mcpServers: allowedServers }, options.preparedNativeMcpPolicy).mcpServers; if (Object.keys(allowedServers).length === 0) return; const grants = options?.agentId ? loadMcpToolGrants(options.agentId) : []; const resolvedConfig = await resolveMcpBearerBundleConfig({ config: { mcpServers: allowedServers }, cfg, agentDir: options?.agentDir, tokenProjection: "literal", omitUnavailableOAuthServers: true, onServerUnavailable: options?.onServerUnavailable }); const mcp_servers = Object.fromEntries(Object.entries(resolvedConfig.config.mcpServers).map(([name, server]) => [name, normalizeCodexMcpServerConfig(name, applyCodexSessionMcpToolDenials(name, server, options?.toolOverrides), grants)])); return Object.keys(mcp_servers).length === 0 ? void 0 : { mcp_servers }; } /** Prepares canonical native MCP policy and projects it into Codex before thread creation. */ async function buildCodexUserMcpServersThreadConfigPatchForRun(params) { const run = params.run; const agentId = params.agentId ?? run.agentId; const scopedToolOverrides = resolveCodexMcpToolOverridesForAgent(run.config, { agentId, toolOverrides: run.toolOverrides }); const policySessionKey = run.sandboxSessionKey ?? run.sessionKey; const policyAgentId = resolveSessionAgentId({ config: run.config, sessionKey: policySessionKey, agentId: run.sandboxAgentId, fallbackAgentId: agentId }); const sandboxStatus = resolveSandboxRuntimeStatus({ cfg: run.config, sessionKey: policySessionKey, agentId: policyAgentId }); const capabilityProfile = resolveConversationCapabilityProfile({ config: run.config, sessionKey: policySessionKey, runSessionKey: run.sessionKey && run.sessionKey !== policySessionKey ? run.sessionKey : void 0, sessionId: run.sessionId, runId: run.runId, agentId: policyAgentId, agentDir: run.agentDir, agentAccountId: run.agentAccountId, messageProvider: run.messageProvider ?? run.messageChannel, messageChannel: run.messageChannel, chatType: run.chatType, messageTo: run.messageTo, messageThreadId: run.messageThreadId, currentChannelId: run.currentChannelId, currentMessagingTarget: run.currentMessagingTarget, currentThreadTs: run.currentThreadTs, currentMessageId: run.currentMessageId, groupId: run.groupId, groupChannel: run.groupChannel, groupSpace: run.groupSpace, memberRoleIds: run.memberRoleIds, spawnedBy: run.spawnedBy, senderId: run.senderId, senderName: run.senderName, senderUsername: run.senderUsername, senderE164: run.senderE164, senderIsOwner: run.senderIsOwner, modelProvider: run.provider, modelId: run.modelId, modelApi: run.model?.api, modelContextWindowTokens: run.model?.contextWindow, modelHasVision: run.model?.input?.includes("image") ?? false, workspaceDir: run.workspaceDir, cwd: params.cwd, skillsSnapshot: run.skillsSnapshot, sandboxToolPolicy: sandboxStatus.sandboxed ? sandboxStatus.toolPolicy : void 0, runtimeToolAllowlist: run.toolsAllow, inheritRuntimeToolAllowlist: true, runtimePluginToolGrant: run.runtimePluginToolGrant, inputProvenance: run.inputProvenance, trustedInternalHandoff: run.trustedInternalHandoff, scheduledToolPolicy: run.scheduledToolPolicy }); const configuredMcpServers = selectCodexProjectableMcpServers(run.config, { agentId, allowLiteralOAuthProjection: params.allowLiteralOAuthProjection, onServerUnavailable: params.onServerUnavailable, toolOverrides: scopedToolOverrides }); if (Object.keys(configuredMcpServers).length === 0) return; const projectionConfig = { ...run.config, mcp: { ...run.config?.mcp, servers: configuredMcpServers } }; const acquisition = await acquireSessionMcpRuntime({ sessionId: run.sessionId, sessionKey: run.sessionKey, workspaceDir: run.workspaceDir, agentDir: run.agentDir, cfg: projectionConfig, requesterSenderId: run.senderId, agentAccountId: run.agentAccountId, messageChannel: run.messageChannel, toolOverrides: scopedToolOverrides }); let preparedNativeMcpPolicy; try { preparedNativeMcpPolicy = await prepareNativeMcpPolicy({ runtime: acquisition.runtime, config: run.config, workspaceDir: run.workspaceDir, capabilityProfile, runtimeToolsAllow: run.toolsAllow, warn: params.warn ?? (() => {}) }); } finally { await releaseSessionMcpRuntime(acquisition); } return await buildCodexUserMcpServersThreadConfigPatchForRuntime(projectionConfig, { agentId, agentDir: run.agentDir, allowLiteralOAuthProjection: params.allowLiteralOAuthProjection, onServerUnavailable: params.onServerUnavailable, toolOverrides: scopedToolOverrides, preparedNativeMcpPolicy }); } //#endregion export { resolveCodexMcpToolOverridesForAgent as a, preparedNativeMcpDenials as c, injectCodexMcpConfigArgs as i, buildCodexUserMcpServersThreadConfigPatchForRun as n, applyPreparedNativeMcpPolicy as o, buildCodexUserMcpServersThreadConfigPatchForRuntime as r, prepareNativeMcpPolicy as s, buildCodexUserMcpServersThreadConfigPatch as t };