openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
301 lines (300 loc) • 14.6 kB
JavaScript
import { t as stableStringify } from "./stable-stringify-Djn3uxNo.mjs";
import { v as resolveSessionAgentIds } from "./agent-scope-CG17uXFn.mjs";
import { l as normalizeToolPolicyName } from "./tool-policy-shared-DWHChhWM.mjs";
import "./tool-policy-BKvvmcQR.mjs";
import { f as readAdmittedRunOperatorAuthority } from "./admitted-run-context-QJnFIxUZ.mjs";
import { r as getPluginToolMeta } from "./tool-metadata-DEC0Rqn3.mjs";
import { a as DirectoryCache } from "./target-resolver-zetQFKNJ.mjs";
import { r as hasSessionControlAuthority } from "./conversation-capability-profile-BL2iKi3w.mjs";
import { a as getInProcessGatewayToolContext } from "./in-process-gateway-B-tN4V5F.mjs";
import { i as withReadySessionRows } from "./session-row-prepared-read-XTl_DJgr.mjs";
import { n as getSessionRowProjection } from "./session-row-projection-access-C2RDnj28.mjs";
import { r as loadPairedComputerUseAvailabilityForSurface } from "./computer-tool-xn2S9vJM.mjs";
import { n as listCoreToolFactoryDescriptors, t as isCoreCodingSurfaceToolName } from "./core-tool-factory-descriptors-DYpD_U2M.mjs";
import { t as applyEmbeddedAttemptToolsAllow } from "./attempt-tool-construction-plan-7h3_PwTS.mjs";
import { n as readMcpLoopbackToolName, t as buildMcpToolSchema } from "./mcp-http.schema-DgFmKp3d.mjs";
import { t as loadNodeExecAvailability } from "./node-exec-availability-nlXaG0Ej.mjs";
import { t as assertCompletionGrantLineage } from "./tool-resolution-completion-CkQwUgGP.mjs";
import { t as resolveGatewayScopedTools } from "./tool-resolution-BfWXIIPw.mjs";
//#region src/gateway/mcp-http.runtime.ts
const TOOL_CACHE_TTL_MS = 3e4;
const TOOL_CACHE_MAX_ENTRIES = 256;
const NATIVE_TOOL_EXCLUDE = new Set(listCoreToolFactoryDescriptors().map(({ name }) => name).filter(isCoreCodingSurfaceToolName));
function captureMcpLoopbackScope(params) {
const canPreparePortal = params.context.senderIsOwner === false && Boolean(params.context.sessionKey.trim() && params.context.sessionId) && !params.rootedExecution?.sandbox;
const gateway = canPreparePortal ? getInProcessGatewayToolContext() : void 0;
const projection = gateway ? getSessionRowProjection(gateway) : void 0;
const assertCurrent = () => {
params.signal?.throwIfAborted();
assertCompletionGrantLineage(params);
hasSessionControlAuthority(readAdmittedRunOperatorAuthority(params.admittedRunContext));
if (params.isGrantCurrent?.() === false) throw new Error("MCP tool grant is no longer current");
if (canPreparePortal && (getInProcessGatewayToolContext() !== gateway || getSessionRowProjection(gateway) !== projection)) throw new Error("MCP tool preparation belongs to a retired Gateway");
};
assertCurrent();
return {
...params,
projection,
assertCurrent
};
}
function captureMcpCatalogSessionFacts(params) {
if (!params.projection) return;
const { sessionAgentId: agentId } = resolveSessionAgentIds({
config: params.cfg,
sessionKey: params.context.sessionKey,
agentId: params.context.agentId
});
const row = params.projection.capture({
agentId,
key: params.context.sessionKey
});
return {
projection: params.projection,
generation: row?.generation,
revision: row?.databaseFactsRevision
};
}
function isMcpCatalogSessionCurrent(catalog, params) {
const current = captureMcpCatalogSessionFacts(params);
return catalog.sessionFacts?.projection === current?.projection && catalog.sessionFacts?.generation === current?.generation && catalog.sessionFacts?.revision === current?.revision;
}
function resolveMediatedNativeTools(toolsAllow, mode) {
if (mode === "exact") return new Set((toolsAllow ?? []).map((name) => normalizeToolPolicyName(name)).filter((name) => NATIVE_TOOL_EXCLUDE.has(name)));
if (toolsAllow === void 0 || toolsAllow.some((toolName) => normalizeToolPolicyName(toolName) === "*")) return /* @__PURE__ */ new Set();
return new Set(applyEmbeddedAttemptToolsAllow(Array.from(NATIVE_TOOL_EXCLUDE, (name) => ({ name })), toolsAllow).map((tool) => tool.name));
}
async function resolveNodeExecScope(params, mode) {
if (!(!params.rootedExecution && !params.context.trustedInternalHandoff && params.context.nodeExecAllowed === true && !params.defaultMediatedToolNames?.length && resolveMediatedNativeTools(params.context.toolsAllow, mode).size === 0)) return params;
const nodeExecAvailability = await loadNodeExecAvailability(params.signal);
params.assertCurrent();
return {
...params,
nodeExecAvailability
};
}
function isComputerAllowedByMcpScope(params, mode) {
const { toolsAllow } = params.context;
if (mode === "exact") return toolsAllow === void 0 || toolsAllow.some((name) => normalizeToolPolicyName(name) === "computer");
return applyEmbeddedAttemptToolsAllow([{ name: "computer" }], toolsAllow).length === 1;
}
async function resolvePairedComputerNodeScope(params, mode) {
params.assertCurrent();
if (!(isComputerAllowedByMcpScope(params, mode) && params.context.modelHasVision !== false && !params.rootedExecution)) return { params };
const policyResolved = await resolveMcpLoopbackTools(params, mode);
params.assertCurrent();
const computerAllowed = policyResolved.tools.some((tool) => readMcpLoopbackToolName(tool) === "computer");
const pairedComputerUseAvailability = await loadPairedComputerUseAvailabilityForSurface({
computerAllowed,
modelHasVision: params.context.modelHasVision,
computerTransport: params.rootedExecution ? null : void 0,
signal: params.signal
});
params.assertCurrent();
if (!pairedComputerUseAvailability) return {
params,
policyResolved
};
const resolvedParams = { params: {
...params,
pairedComputerUseAvailability
} };
return pairedComputerUseAvailability.prepared ? resolvedParams : {
...resolvedParams,
policyResolved
};
}
async function resolveMcpLoopbackTools(params, mode, policyResolved) {
return withReadyMcpSession(params, () => policyResolved && isMcpCatalogSessionCurrent(policyResolved, params) ? policyResolved : constructMcpLoopbackTools(params, mode));
}
async function withReadyMcpSession(params, consume) {
params.assertCurrent();
const { sessionKey, sessionId, senderIsOwner } = params.context;
if (!params.projection || !sessionId || senderIsOwner !== false) return consume();
const { sessionAgentId: agentId } = resolveSessionAgentIds({
config: params.cfg,
sessionKey,
agentId: params.context.agentId
});
return await withReadySessionRows(params.projection, () => [{
agentId,
key: sessionKey
}], () => {
params.assertCurrent();
return consume();
});
}
function constructMcpLoopbackTools(params, mode) {
params.assertCurrent();
const sessionFacts = captureMcpCatalogSessionFacts(params);
const { toolsAllow, webSearchDisabled, ...context } = params.context;
const excludeToolNames = new Set(NATIVE_TOOL_EXCLUDE);
if (webSearchDisabled) excludeToolNames.add("web_search");
const mediatedNativeTools = params.rootedExecution || context.trustedInternalHandoff ? new Set(NATIVE_TOOL_EXCLUDE) : resolveMediatedNativeTools(toolsAllow, mode);
for (const toolName of params.defaultMediatedToolNames ?? []) {
const name = normalizeToolPolicyName(toolName);
if (!NATIVE_TOOL_EXCLUDE.has(name)) throw new Error(`Unknown host-owned coding tool: ${toolName}`);
mediatedNativeTools.add(name);
}
for (const toolName of mediatedNativeTools) excludeToolNames.delete(toolName);
const includeNodeExecTool = context.nodeExecAllowed === true && mediatedNativeTools.size === 0;
if (includeNodeExecTool) excludeToolNames.delete("exec");
const skillWorkshop = context.skillWorkshop || params.skillLibraryAuthoring ? {
...context.skillWorkshop,
libraryAuthoring: params.skillLibraryAuthoring
} : void 0;
const scoped = resolveGatewayScopedTools({
...context,
rootedExecution: params.rootedExecution,
messageActionTurnCapability: params.messageActionTurnCapability,
cfg: params.cfg,
authProfileStore: params.authProfileStore,
onYield: params.onYield,
skillWorkshop,
nativeCronCreatorToolAllowlist: context.nativeCronCreatorToolAllowlist ?? void 0,
agentDir: params.authProfileStoreAgentDir,
conversationReadOrigin: "delegated",
surface: "loopback",
admittedRunContext: params.admittedRunContext,
isGrantCurrent: params.isGrantCurrent,
excludeToolNames,
mediatedToolNames: mediatedNativeTools,
includeNodeExecTool,
nodeExecAvailable: params.nodeExecAvailability?.isAvailable,
pairedNodeComputerUse: params.pairedComputerUseAvailability?.prepared
});
const tools = mode === "exact" ? applyGrantToolsAllow(scoped.tools, toolsAllow) : applyPolicyToolsAllow(scoped.tools, toolsAllow);
const toolSchema = buildMcpToolSchema(tools);
scoped.captureFinalCronCreatorTools?.(new Set(toolSchema.map((tool) => tool.name)));
return {
agentId: scoped.agentId,
workspaceDir: scoped.workspaceDir,
tools,
toolSchema,
sessionFacts
};
}
async function resolveMcpLoopbackCatalog(params, mode) {
const resolved = await resolvePairedComputerNodeScope(await resolveNodeExecScope(captureMcpLoopbackScope(params), mode), mode);
for (;;) {
const tools = await resolveMcpLoopbackTools(resolved.params, mode, resolved.policyResolved);
resolved.params.assertCurrent();
if (isMcpCatalogSessionCurrent(tools, resolved.params)) return tools;
}
}
/** Resolves loopback-visible tools from the exact names carried by a minted grant. */
function resolveMcpLoopbackScopedTools(params) {
return resolveMcpLoopbackCatalog(params, "exact");
}
/** Materializes runtime policy expressions against the concrete loopback catalog. */
function resolveMcpLoopbackPolicyTools(params) {
return resolveMcpLoopbackCatalog(params, "policy");
}
/**
* Hard-enforces a per-run grant allowlist on the loopback surface. Both
* tools/list and tools/call consume this list, so a tool outside the
* allowlist can be neither discovered nor executed even when the CLI runs
* with a bypass permission mode. An empty allowlist fails closed.
*/
function applyGrantToolsAllow(tools, toolsAllow) {
if (!toolsAllow) return tools;
const allowed = new Set(toolsAllow.map((name) => normalizeToolPolicyName(name)).filter(Boolean));
return tools.filter((tool) => {
const name = readMcpLoopbackToolName(tool);
return name !== void 0 && allowed.has(normalizeToolPolicyName(name));
});
}
function applyPolicyToolsAllow(tools, toolsAllow) {
if (!toolsAllow) return tools;
const candidates = tools.flatMap((tool) => {
const name = readMcpLoopbackToolName(tool);
return name ? [{
name,
tool
}] : [];
});
return applyEmbeddedAttemptToolsAllow(candidates, toolsAllow, { toolMeta: (candidate) => getPluginToolMeta(candidate.tool) }).map((candidate) => candidate.tool);
}
function buildMcpLoopbackToolCacheKey(params) {
const { context } = params;
return `${params.grantToken ?? ""}\u0000${stableStringify({
context: {
...context,
clientCaps: [...new Set(context.clientCaps ?? [])].toSorted(),
toolsAllow: context.toolsAllow ? [...new Set(context.toolsAllow)].toSorted() : void 0,
pinnedWidgetAuthoring: context.pinnedWidgetAuthoring === true,
currentInboundAudio: context.currentInboundAudio === true,
sourceReplyOnly: context.sourceReplyOnly === true,
requireExplicitMessageTarget: context.requireExplicitMessageTarget === true,
nodeExecAllowed: context.nodeExecAllowed === true,
delegationCapability: context.delegationCapability === "report_only" ? "report_only" : void 0
},
admittedRunInstance: params.admittedRunContext?.operationalRunInstance,
defaultMediatedToolNames: params.defaultMediatedToolNames,
sessionControlsAllowed: hasSessionControlAuthority(readAdmittedRunOperatorAuthority(params.admittedRunContext)),
authProfileStoreAgentDir: params.authProfileStoreAgentDir,
yieldContextCacheKey: params.yieldContextCacheKey,
nodeExecAvailability: params.nodeExecAvailability?.cacheKey,
pairedComputerUseAvailability: params.pairedComputerUseAvailability?.cacheKey
})}`;
}
/** Short-lived cache for loopback tool lists keyed by session/channel context. */
var McpLoopbackToolCache = class {
#entries = new DirectoryCache(TOOL_CACHE_TTL_MS, TOOL_CACHE_MAX_ENTRIES);
#grantConfigScopes = /* @__PURE__ */ new Map();
#epoch = 0;
async resolve(input) {
const epoch = this.#epoch;
const nodeExecParams = await resolveNodeExecScope(captureMcpLoopbackScope(input), "exact");
for (;;) {
nodeExecParams.assertCurrent();
const preDiscoveryCacheKey = buildMcpLoopbackToolCacheKey(nodeExecParams);
let preDiscoveryCached = this.#entries.get(preDiscoveryCacheKey, nodeExecParams.cfg);
if (preDiscoveryCached) {
preDiscoveryCached = await withReadyMcpSession(nodeExecParams, () => {
const cached = this.#entries.get(preDiscoveryCacheKey, nodeExecParams.cfg);
return cached && isMcpCatalogSessionCurrent(cached, nodeExecParams) ? cached : void 0;
});
nodeExecParams.assertCurrent();
if (preDiscoveryCached && isMcpCatalogSessionCurrent(preDiscoveryCached, nodeExecParams)) return preDiscoveryCached;
}
const resolved = await resolvePairedComputerNodeScope(nodeExecParams, "exact");
nodeExecParams.assertCurrent();
const { params } = resolved;
const cacheKey = buildMcpLoopbackToolCacheKey(params);
const nextEntry = await withReadyMcpSession(params, () => {
const cached = this.#entries.get(cacheKey, params.cfg);
if (cached && isMcpCatalogSessionCurrent(cached, params)) return cached;
const next = resolved.policyResolved && isMcpCatalogSessionCurrent(resolved.policyResolved, params) ? resolved.policyResolved : constructMcpLoopbackTools(params, "exact");
params.assertCurrent();
if (epoch === this.#epoch) {
this.#entries.set(cacheKey, next, params.cfg);
if (params.grantToken) {
const scopes = this.#grantConfigScopes.get(params.grantToken) ?? /* @__PURE__ */ new Set();
scopes.add(params.cfg);
this.#grantConfigScopes.set(params.grantToken, scopes);
}
}
return next;
});
params.assertCurrent();
if (isMcpCatalogSessionCurrent(nextEntry, params)) return nextEntry;
}
}
evictGrant(token) {
this.#epoch += 1;
const scopes = this.#grantConfigScopes.get(token);
if (!scopes) return false;
const cacheKeyPrefix = `${token}\u0000`;
for (const cfg of scopes) this.#entries.clearMatching((cacheKey) => cacheKey.startsWith(cacheKeyPrefix), cfg);
this.#grantConfigScopes.delete(token);
return true;
}
clear() {
this.#epoch += 1;
this.#entries.clear();
this.#grantConfigScopes.clear();
}
};
//#endregion
export { resolveMcpLoopbackPolicyTools as n, resolveMcpLoopbackScopedTools as r, McpLoopbackToolCache as t };