@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
848 lines • 33.8 kB
JavaScript
import { tool, } from "./tool.mjs";
import { UserError } from "./errors.mjs";
import { bindMcpTool, getMcpToolBinding } from "./toolIdentity.mjs";
import { MCPServerStdio as UnderlyingMCPServerStdio, MCPServerStreamableHttp as UnderlyingMCPServerStreamableHttp, MCPServerSSE as UnderlyingMCPServerSSE, } from '@openai/agents-core/_shims';
import { getCurrentSpan, getCurrentTrace, withMCPListToolsSpan, } from "./tracing/index.mjs";
import { logger as globalLogger, logToolActionWarning, } from "./logger.mjs";
import { getMcpServerExternalName } from "./mcpLogging.mjs";
import { maybeExtractToolOutputCustomData } from "./utils/customData.mjs";
import { BaseMCPServerSSE, BaseMCPServerStdio, BaseMCPServerStreamableHttp, } from "./mcpShared.mjs";
import { beginServerToolsCacheListing, cachedMcpToolKeysByServer as _cachedToolKeysByServer, cachedMcpTools as _cachedTools, } from "./mcpToolCache.mjs";
import { getToolCallParentSpanFromDetails } from "./agentToolRunConfig.mjs";
import { assertOpenAIStrictToolSchemaPreservesOpenObjects, isJsonSchemaDepthError, } from "./utils/strictToolSchema.mjs";
import { toFunctionToolName } from "./utils/tools.mjs";
export { BaseMCPServerSSE, BaseMCPServerStdio, BaseMCPServerStreamableHttp, DEFAULT_SSE_MCP_CLIENT_LOGGER_NAME, DEFAULT_STDIO_MCP_CLIENT_LOGGER_NAME, DEFAULT_STREAMABLE_HTTP_MCP_CLIENT_LOGGER_NAME, MCPTool, attachCallToolResultMetadata, } from "./mcpShared.mjs";
export { invalidateServerToolsCache } from "./mcpToolCache.mjs";
const MCP_FUNCTION_TOOL_NAME_MAX_LENGTH = 64;
const MCP_FUNCTION_TOOL_HASH_LENGTH = 8;
function snapshotMcpTools(tools) {
return structuredClone(tools);
}
class MCPToolsLifecycleGuard {
generation = 0;
activeOperations = 0;
startLifecycleBranch(operation) {
try {
return operation?.() ?? Promise.resolve();
}
catch (error) {
return Promise.reject(error);
}
}
beginLifecycleOperation() {
this.activeOperations += 1;
this.generation += 1;
}
endLifecycleOperation() {
this.activeOperations -= 1;
}
async runLifecycleOperation(invalidate, operation) {
this.beginLifecycleOperation();
try {
const [invalidationResult, operationResult] = await Promise.allSettled([
this.startLifecycleBranch(invalidate),
this.startLifecycleBranch(operation),
]);
if (invalidationResult.status === 'rejected') {
throw invalidationResult.reason;
}
if (operationResult.status === 'rejected') {
throw operationResult.reason;
}
}
finally {
this.endLifecycleOperation();
}
}
invalidate() {
this.generation += 1;
}
beginListing() {
if (this.activeOperations > 0) {
throw new Error('Cannot list MCP tools while a server lifecycle operation is in progress.');
}
return this.generation;
}
assertListingIsCurrent(listingGeneration) {
if (this.activeOperations > 0 || listingGeneration !== this.generation) {
throw new Error('MCP tool listing became stale before it completed.');
}
}
}
/**
* Public interface of an MCP server that provides tools.
* You can use this class to pass MCP server settings to your agent.
*/
export class MCPServerStdio extends BaseMCPServerStdio {
underlying;
toolsLifecycle = new MCPToolsLifecycleGuard();
constructor(options) {
super(options);
this.underlying = new UnderlyingMCPServerStdio(options);
}
get name() {
return this.underlying.name;
}
async invalidateToolsCaches() {
this._cachedTools = undefined;
await this.underlying.invalidateToolsCache();
}
connect() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches(), () => this.underlying.connect());
}
close() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches(), () => this.underlying.close());
}
async listTools() {
const listingGeneration = this.toolsLifecycle.beginListing();
if (this.cacheToolsList && this._cachedTools) {
return snapshotMcpTools(this._cachedTools);
}
const tools = await this.underlying.listTools();
this.toolsLifecycle.assertListingIsCurrent(listingGeneration);
if (this.cacheToolsList) {
this._cachedTools = snapshotMcpTools(tools);
return snapshotMcpTools(this._cachedTools);
}
return snapshotMcpTools(tools);
}
async callTool(toolName, args, meta, options) {
return (await this.callToolResult(toolName, args, meta, options)).content;
}
callToolResult(toolName, args, meta, options) {
return this.underlying.callToolResult(toolName, args, meta, options);
}
listResources(params) {
return this.underlying.listResources(params);
}
listResourceTemplates(params) {
return this.underlying.listResourceTemplates(params);
}
readResource(uri) {
return this.underlying.readResource(uri);
}
invalidateToolsCache() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches());
}
}
export class MCPServerStreamableHttp extends BaseMCPServerStreamableHttp {
underlying;
_cachedToolsSessionId = undefined;
toolsLifecycle = new MCPToolsLifecycleGuard();
constructor(options) {
super(options);
this.underlying = new UnderlyingMCPServerStreamableHttp(options);
}
clearLocalToolsCache() {
this._cachedTools = undefined;
this._cachedToolsSessionId = undefined;
}
async invalidateToolsCaches() {
this.clearLocalToolsCache();
await this.underlying.invalidateToolsCache();
}
get name() {
return this.underlying.name;
}
get sessionId() {
return this.underlying.sessionId;
}
connect() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches(), () => this.underlying.connect());
}
close() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches(), () => this.underlying.close());
}
async listTools() {
const listingGeneration = this.toolsLifecycle.beginListing();
const sessionId = this.sessionId;
if (sessionId === undefined) {
this.toolsLifecycle.invalidate();
await this.invalidateToolsCaches();
const tools = await this.underlying.listTools();
return snapshotMcpTools(tools);
}
if (this.cacheToolsList &&
this._cachedTools &&
this._cachedToolsSessionId === sessionId) {
return snapshotMcpTools(this._cachedTools);
}
const tools = await this.underlying.listTools();
this.toolsLifecycle.assertListingIsCurrent(listingGeneration);
if (this.cacheToolsList) {
this._cachedTools = snapshotMcpTools(tools);
this._cachedToolsSessionId = sessionId;
return snapshotMcpTools(this._cachedTools);
}
return snapshotMcpTools(tools);
}
async callTool(toolName, args, meta, options) {
return (await this.callToolResult(toolName, args, meta, options)).content;
}
async callToolResult(toolName, args, meta, options) {
const previousSessionId = this.sessionId;
try {
return await this.underlying.callToolResult(toolName, args, meta, options);
}
finally {
if (previousSessionId !== this.sessionId) {
this.toolsLifecycle.invalidate();
await this.invalidateToolsCaches();
}
}
}
listResources(params) {
return this.underlying.listResources(params);
}
listResourceTemplates(params) {
return this.underlying.listResourceTemplates(params);
}
readResource(uri) {
return this.underlying.readResource(uri);
}
invalidateToolsCache() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches());
}
}
export class MCPServerSSE extends BaseMCPServerSSE {
underlying;
toolsLifecycle = new MCPToolsLifecycleGuard();
constructor(options) {
super(options);
this.underlying = new UnderlyingMCPServerSSE(options);
}
get name() {
return this.underlying.name;
}
async invalidateToolsCaches() {
this._cachedTools = undefined;
await this.underlying.invalidateToolsCache();
}
connect() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches(), () => this.underlying.connect());
}
close() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches(), () => this.underlying.close());
}
async listTools() {
const listingGeneration = this.toolsLifecycle.beginListing();
if (this.cacheToolsList && this._cachedTools) {
return snapshotMcpTools(this._cachedTools);
}
const tools = await this.underlying.listTools();
this.toolsLifecycle.assertListingIsCurrent(listingGeneration);
if (this.cacheToolsList) {
this._cachedTools = snapshotMcpTools(tools);
return snapshotMcpTools(this._cachedTools);
}
return snapshotMcpTools(tools);
}
async callTool(toolName, args, meta, options) {
return (await this.callToolResult(toolName, args, meta, options)).content;
}
callToolResult(toolName, args, meta, options) {
return this.underlying.callToolResult(toolName, args, meta, options);
}
listResources(params) {
return this.underlying.listResources(params);
}
listResourceTemplates(params) {
return this.underlying.listResourceTemplates(params);
}
readResource(uri) {
return this.underlying.readResource(uri);
}
invalidateToolsCache() {
return this.toolsLifecycle.runLifecycleOperation(() => this.invalidateToolsCaches());
}
}
/**
* Default cache key generator for MCP tools.
* Uses server name, or server+agent if using callable filter.
*/
export const defaultMCPToolCacheKey = ({ server, agent, }) => {
if (server.toolFilter && typeof server.toolFilter === 'function' && agent) {
return `${server.name}:${agent.name}`;
}
return server.name;
};
function logMcpToolFilterDebug(buildMessage) {
if (!globalLogger.dontLogToolData) {
globalLogger.debug(buildMessage());
}
}
/**
* Fetches and filters raw MCP tools from a single MCP server.
*/
async function getMcpToolsFromServer({ server, runContext, agent, generateMCPToolCacheKey, tracingParent, }) {
const cacheKey = (generateMCPToolCacheKey || defaultMCPToolCacheKey)({
server,
agent,
runContext,
});
const serverName = server.name;
// Use cache key generator injected from the outside, or the default if absent.
const cachedMcpTools = server.cacheToolsList
? _cachedTools[cacheKey]
: undefined;
const cacheListing = beginServerToolsCacheListing(serverName);
const listToolsForServer = async (span) => {
const fetchedMcpTools = snapshotMcpTools(cachedMcpTools ?? (await server.listTools()));
let mcpTools = fetchedMcpTools;
if (server.toolFilter) {
const filteredTools = [];
for (const tool of fetchedMcpTools) {
const filter = server.toolFilter;
if (filter) {
if (typeof filter === 'function') {
if (!runContext || !agent) {
filteredTools.push(tool);
continue;
}
const context = { runContext, agent, serverName: server.name };
const [detachedTool] = snapshotMcpTools([tool]);
const filtered = await filter(context, detachedTool);
if (!filtered) {
logMcpToolFilterDebug(() => `MCP Tool (server: ${getMcpServerExternalName(server.name)}, tool: ${tool.name}) is blocked by the callable filter.`);
continue;
}
}
else {
const allowedToolNames = filter.allowedToolNames ?? [];
const blockedToolNames = filter.blockedToolNames ?? [];
if (allowedToolNames.length > 0 || blockedToolNames.length > 0) {
const allowed = allowedToolNames.length > 0
? allowedToolNames.includes(tool.name)
: true;
const blocked = blockedToolNames.length > 0
? blockedToolNames.includes(tool.name)
: false;
if (!allowed || blocked) {
if (blocked) {
logMcpToolFilterDebug(() => `MCP Tool (server: ${getMcpServerExternalName(server.name)}, tool: ${tool.name}) is blocked by the static filter.`);
}
else if (!allowed) {
logMcpToolFilterDebug(() => `MCP Tool (server: ${getMcpServerExternalName(server.name)}, tool: ${tool.name}) is not allowed by the static filter.`);
}
continue;
}
}
}
}
filteredTools.push(tool);
}
mcpTools = filteredTools;
}
if (span) {
span.spanData.result = mcpTools.map((t) => t.name);
}
// Cache discovery only; filter decisions belong to the current lookup.
if (!cachedMcpTools && server.cacheToolsList && cacheListing.isCurrent()) {
_cachedTools[cacheKey] = snapshotMcpTools(fetchedMcpTools);
if (!_cachedToolKeysByServer[serverName]) {
_cachedToolKeysByServer[serverName] = new Set();
}
_cachedToolKeysByServer[serverName].add(cacheKey);
}
return snapshotMcpTools(mcpTools);
};
try {
if (cachedMcpTools || (!tracingParent && !getCurrentTrace())) {
return await listToolsForServer();
}
return await withMCPListToolsSpan(listToolsForServer, {
data: { server: getMcpServerExternalName(server.name) },
}, tracingParent);
}
finally {
cacheListing.release();
}
}
function convertMcpToolsToFunctionTools({ mcpTools, server, convertSchemasToStrict, toolNameOverrides, serverIndex = null, errorFunction, }) {
return mcpTools.map((mcpTool, index) => {
const converted = mcpToFunctionTool(mcpTool, server, convertSchemasToStrict, {
toolNameOverride: toolNameOverrides?.[index],
errorFunction,
});
return bindMcpTool(converted, {
...getMcpToolBinding(converted),
serverIndex,
});
});
}
/**
* Fetches all function tools from a single MCP server.
*/
async function getFunctionToolsFromServer({ server, serverIndex, convertSchemasToStrict, runContext, agent, generateMCPToolCacheKey, errorFunction, tracingParent, }) {
const mcpTools = await getMcpToolsFromServer({
server,
runContext,
agent,
generateMCPToolCacheKey,
tracingParent,
});
return convertMcpToolsToFunctionTools({
mcpTools,
serverIndex,
server,
convertSchemasToStrict,
errorFunction,
});
}
/**
* Returns all MCP tools from the provided servers, using the function tool conversion.
* If runContext and agent are provided, callable tool filters will be applied.
*/
export async function getAllMcpTools(mcpServersOrOpts, runContext, agent, convertSchemasToStrict = false) {
const opts = Array.isArray(mcpServersOrOpts)
? {
mcpServers: mcpServersOrOpts,
runContext,
agent,
convertSchemasToStrict,
}
: mcpServersOrOpts;
const { mcpServers, convertSchemasToStrict: convertSchemasToStrictFromOpts = false, runContext: runContextFromOpts, agent: agentFromOpts, generateMCPToolCacheKey, errorFunction, includeServerInToolNames = false, reservedToolNames, tracingParent, } = opts;
const allTools = [];
const toolNames = new Set();
if (includeServerInToolNames) {
const serverToolBatches = await Promise.all(mcpServers.map(async (server, serverIndex) => ({
server,
serverIndex,
mcpTools: await getMcpToolsFromServer({
server,
runContext: runContextFromOpts,
agent: agentFromOpts,
generateMCPToolCacheKey,
tracingParent,
}),
})));
const toolNameOverrides = buildPrefixedToolNameOverrides(serverToolBatches, new Set(reservedToolNames ?? []));
for (const { server, serverIndex, mcpTools } of serverToolBatches) {
const serverTools = convertMcpToolsToFunctionTools({
mcpTools,
server,
serverIndex,
convertSchemasToStrict: convertSchemasToStrictFromOpts,
errorFunction,
toolNameOverrides: mcpTools.map((_, toolIndex) => toolNameOverrides.get(getToolNameOverrideKey(serverIndex, toolIndex))),
});
const duplicateToolNames = findDuplicateToolNames(serverTools.map((t) => t.name), toolNames);
if (duplicateToolNames.length > 0) {
throw new UserError(`Duplicate tool names found across MCP servers: ${duplicateToolNames.join(', ')}`);
}
for (const t of serverTools) {
toolNames.add(t.name);
allTools.push(t);
}
}
return allTools;
}
for (const [serverIndex, server] of mcpServers.entries()) {
const serverTools = await getFunctionToolsFromServer({
server,
serverIndex,
convertSchemasToStrict: convertSchemasToStrictFromOpts,
runContext: runContextFromOpts,
agent: agentFromOpts,
generateMCPToolCacheKey,
errorFunction,
tracingParent,
});
const duplicateToolNames = findDuplicateToolNames(serverTools.map((t) => t.name), toolNames);
if (duplicateToolNames.length > 0) {
throw new UserError(`Duplicate tool names found across MCP servers: ${duplicateToolNames.join(', ')}`);
}
for (const t of serverTools) {
toolNames.add(t.name);
allTools.push(t);
}
}
return allTools;
}
/**
* Returns the names that repeat within `names` or were already seen in
* `previousNames`.
*/
function findDuplicateToolNames(names, previousNames) {
const seen = new Set(previousNames);
const duplicates = new Set();
for (const name of names) {
if (seen.has(name)) {
duplicates.add(name);
}
seen.add(name);
}
return [...duplicates].sort();
}
function getToolNameOverrideKey(serverIndex, toolIndex) {
return `${serverIndex}:${toolIndex}`;
}
function getSafeToolNamePart(value, fallback) {
const safe = Array.from(value)
.map((char) => {
const code = char.charCodeAt(0);
return code <= 127 && /[A-Za-z0-9_-]/.test(char) ? char : '_';
})
.join('')
.replace(/^[_-]+|[_-]+$/g, '');
return safe || fallback;
}
function getUtf8Bytes(value) {
const encoded = encodeURIComponent(value);
const bytes = [];
for (let index = 0; index < encoded.length; index += 1) {
if (encoded[index] === '%') {
bytes.push(Number.parseInt(encoded.slice(index + 1, index + 3), 16));
index += 2;
}
else {
bytes.push(encoded.charCodeAt(index));
}
}
return bytes;
}
function rotateLeft(value, bits) {
return ((value << bits) | (value >>> (32 - bits))) >>> 0;
}
function getSha1Hex(value) {
const bytes = getUtf8Bytes(value);
const bitLength = bytes.length * 8;
bytes.push(0x80);
while (bytes.length % 64 !== 56) {
bytes.push(0);
}
for (let shift = 56; shift >= 0; shift -= 8) {
bytes.push(Math.floor(bitLength / 2 ** shift) & 0xff);
}
let h0 = 0x67452301;
let h1 = 0xefcdab89;
let h2 = 0x98badcfe;
let h3 = 0x10325476;
let h4 = 0xc3d2e1f0;
for (let offset = 0; offset < bytes.length; offset += 64) {
const words = new Array(80).fill(0);
for (let index = 0; index < 16; index += 1) {
const byteOffset = offset + index * 4;
words[index] =
((bytes[byteOffset] ?? 0) << 24) |
((bytes[byteOffset + 1] ?? 0) << 16) |
((bytes[byteOffset + 2] ?? 0) << 8) |
(bytes[byteOffset + 3] ?? 0);
}
for (let index = 16; index < 80; index += 1) {
words[index] = rotateLeft(words[index - 3] ^
words[index - 8] ^
words[index - 14] ^
words[index - 16], 1);
}
let a = h0;
let b = h1;
let c = h2;
let d = h3;
let e = h4;
for (let index = 0; index < 80; index += 1) {
let f;
let k;
if (index < 20) {
f = (b & c) | (~b & d);
k = 0x5a827999;
}
else if (index < 40) {
f = b ^ c ^ d;
k = 0x6ed9eba1;
}
else if (index < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8f1bbcdc;
}
else {
f = b ^ c ^ d;
k = 0xca62c1d6;
}
const temp = (rotateLeft(a, 5) + f + e + k + (words[index] ?? 0)) >>> 0;
e = d;
d = c;
c = rotateLeft(b, 30);
b = a;
a = temp;
}
h0 = (h0 + a) >>> 0;
h1 = (h1 + b) >>> 0;
h2 = (h2 + c) >>> 0;
h3 = (h3 + d) >>> 0;
h4 = (h4 + e) >>> 0;
}
return [h0, h1, h2, h3, h4]
.map((word) => word.toString(16).padStart(8, '0'))
.join('');
}
function shortenToolName(baseName, seed, forceHash = false) {
if (!forceHash && baseName.length <= MCP_FUNCTION_TOOL_NAME_MAX_LENGTH) {
return baseName;
}
const hashSuffix = getSha1Hex(seed).slice(0, MCP_FUNCTION_TOOL_HASH_LENGTH);
const suffix = `_${hashSuffix}`;
const stemLength = MCP_FUNCTION_TOOL_NAME_MAX_LENGTH - suffix.length;
const stem = baseName.slice(0, stemLength).replace(/[_-]+$/g, '') || 'mcp';
return `${stem}${suffix}`;
}
function buildPrefixedToolBaseName(serverName, toolName) {
const serverPart = getSafeToolNamePart(serverName, 'server');
const toolPart = getSafeToolNamePart(toolName, 'tool');
return `mcp_${serverPart}__${toolPart}`;
}
function buildPrefixedToolNameOverrides(serverToolBatches, reservedNames) {
const baseNameCounts = new Map();
for (const { server, mcpTools } of serverToolBatches) {
const serverName = getMcpServerExternalName(server.name);
for (const mcpTool of mcpTools) {
const baseName = buildPrefixedToolBaseName(serverName, mcpTool.name);
const publicName = toFunctionToolName(baseName);
baseNameCounts.set(publicName, (baseNameCounts.get(publicName) ?? 0) + 1);
}
}
const candidates = [];
const rawServerNamesBySeed = new Map();
for (const { server, serverIndex, mcpTools } of serverToolBatches) {
const serverName = getMcpServerExternalName(server.name);
mcpTools.forEach((mcpTool, toolIndex) => {
const baseName = buildPrefixedToolBaseName(serverName, mcpTool.name);
const seed = `${serverName}\0${mcpTool.name}`;
const previousRawServerName = rawServerNamesBySeed.get(seed);
if (previousRawServerName !== undefined &&
previousRawServerName !== server.name) {
throw new UserError(`MCP server names are indistinguishable after URL redaction for tool '${mcpTool.name}': '${serverName}'. Configure unique safe server names when includeServerInToolNames is enabled.`);
}
rawServerNamesBySeed.set(seed, server.name);
const functionToolName = toFunctionToolName(baseName);
const forceHash = (baseNameCounts.get(functionToolName) ?? 0) > 1 ||
reservedNames.has(functionToolName);
candidates.push({
batchKey: getToolNameOverrideKey(serverIndex, toolIndex),
baseName,
seed,
initialName: shortenToolName(baseName, seed, forceHash),
serverIndex,
toolIndex,
});
});
}
const usedNames = new Set(reservedNames);
const overrides = new Map();
for (const candidate of candidates.sort((left, right) => {
return (left.initialName.localeCompare(right.initialName) ||
left.seed.localeCompare(right.seed) ||
left.serverIndex - right.serverIndex ||
left.toolIndex - right.toolIndex);
})) {
let publicName = candidate.initialName;
let collisionIndex = 1;
while (usedNames.has(toFunctionToolName(publicName))) {
publicName = shortenToolName(candidate.baseName, `${candidate.seed}\0${collisionIndex}`, true);
collisionIndex += 1;
}
usedNames.add(toFunctionToolName(publicName));
overrides.set(candidate.batchKey, publicName);
}
return overrides;
}
async function resolveMcpToolMeta(server, runContext, toolName, args) {
const resolver = server.toolMetaResolver;
if (!resolver) {
return undefined;
}
const context = {
runContext,
serverName: server.name,
toolName,
arguments: args,
};
const resolved = await resolver(context);
if (resolved == null) {
return undefined;
}
if (typeof resolved !== 'object' || Array.isArray(resolved)) {
throw new TypeError('MCP tool meta resolver must return an object or null.');
}
return resolved;
}
export function mcpToFunctionTool(mcpTool, server, convertSchemasToStrict, options = {}) {
const rawToolName = mcpTool.name;
const toolName = options.toolNameOverride ?? rawToolName;
const binding = {
serverName: getMcpServerExternalName(server.name),
toolName: rawToolName,
serverIndex: null,
};
const annotations = mcpTool.annotations === undefined
? undefined
: structuredClone(mcpTool.annotations);
function finishConversion(converted) {
if (annotations !== undefined) {
Object.defineProperty(converted, 'mcpAnnotations', {
get: () => structuredClone(annotations),
});
}
return bindMcpTool(converted, binding);
}
const inputGuardrails = server.toolInputGuardrails
? [...server.toolInputGuardrails]
: undefined;
const outputGuardrails = server.toolOutputGuardrails
? [...server.toolOutputGuardrails]
: undefined;
const customDataByCall = new WeakMap();
const serverErrorFunction = server.errorFunction;
const mcpErrorFunction = serverErrorFunction !== undefined
? serverErrorFunction
: options.errorFunction;
const errorFunction = typeof mcpErrorFunction === 'function'
? (context, error) => mcpErrorFunction({ context, error })
: mcpErrorFunction;
async function invoke(input, runContext, details) {
let args = {};
if (typeof input === 'string' && input) {
args = JSON.parse(input);
}
else if (typeof input === 'object' && input != null) {
args = input;
}
const currentSpan = getToolCallParentSpanFromDetails(details) ?? getCurrentSpan();
if (currentSpan) {
currentSpan.spanData['mcp_data'] = {
server: getMcpServerExternalName(server.name),
};
}
const meta = runContext
? await resolveMcpToolMeta(server, runContext, rawToolName, args)
: undefined;
const callOptions = details?.signal
? { signal: details.signal }
: undefined;
const useFullResult = (server.useStructuredContent === true ||
server.customDataExtractor !== undefined) &&
server.callToolResult !== undefined;
let result;
if (useFullResult) {
result = callOptions
? await server.callToolResult(rawToolName, args, meta, callOptions)
: meta === undefined
? await server.callToolResult(rawToolName, args)
: await server.callToolResult(rawToolName, args, meta);
}
else {
result = {
content: callOptions
? await server.callTool(rawToolName, args, meta, callOptions)
: meta === undefined
? await server.callTool(rawToolName, args)
: await server.callTool(rawToolName, args, meta),
};
}
const content = result.content;
const resultMeta = result._meta ?? content._meta;
const structuredContent = result.structuredContent ?? content.structuredContent;
const isError = result.isError ?? content.isError;
const toolOutput = server.useStructuredContent === true &&
isError !== true &&
structuredContent !== undefined
? JSON.stringify(structuredContent)
: content.length === 1
? content[0]
: content;
if (runContext && details?.toolCall?.callId && server.customDataExtractor) {
let byCall = customDataByCall.get(runContext);
if (!byCall) {
byCall = new Map();
customDataByCall.set(runContext, byCall);
}
byCall.set(details.toolCall.callId, {
runContext,
serverName: server.name,
toolName: rawToolName,
toolDisplayName: toolName,
arguments: cloneMcpCustomDataContextValue(args),
resultMeta: cloneMcpCustomDataContextValue(resultMeta),
structuredContent: cloneMcpCustomDataContextValue(structuredContent),
isError,
toolOutput: cloneMcpCustomDataContextValue(toolOutput),
});
}
return toolOutput;
}
async function extractCustomData(context) {
const mcpContext = getMcpCustomDataContext(customDataByCall, context.runContext, context.toolCall.callId);
return mcpContext
? maybeExtractToolOutputCustomData(server.customDataExtractor, {
...mcpContext,
toolOutput: context.output,
})
: undefined;
}
const inputSchema = mcpTool.inputSchema ?? {};
const inputSchemaIsEmpty = Object.keys(inputSchema).length === 0;
const schema = {
...inputSchema,
type: inputSchema.type ?? 'object',
properties: inputSchema.properties ?? {},
required: inputSchema.required ?? [],
};
const strictSchema = (inputSchemaIsEmpty ? { ...schema, additionalProperties: false } : schema);
let preserveSchemaOnFallback = false;
if (convertSchemasToStrict || schema.additionalProperties === true) {
try {
assertOpenAIStrictToolSchemaPreservesOpenObjects(strictSchema);
return finishConversion(tool({
name: toolName,
description: mcpTool.description || '',
parameters: strictSchema,
strict: true,
execute: invoke,
errorFunction,
inputGuardrails,
outputGuardrails,
customDataExtractor: extractCustomData,
}));
}
catch (e) {
if (convertSchemasToStrict && isJsonSchemaDepthError(e)) {
throw e;
}
preserveSchemaOnFallback = true;
logToolActionWarning(globalLogger, 'Error converting MCP schema to strict mode:', e);
}
}
const nonStrictSchema = {
...schema,
additionalProperties: preserveSchemaOnFallback && 'additionalProperties' in schema
? schema.additionalProperties
: true,
};
return finishConversion(tool({
name: toolName,
description: mcpTool.description || '',
parameters: nonStrictSchema,
strict: false,
execute: invoke,
errorFunction,
inputGuardrails,
outputGuardrails,
customDataExtractor: extractCustomData,
}));
}
function getMcpCustomDataContext(contexts, runContext, callId) {
const byCall = contexts.get(runContext);
const context = byCall?.get(callId);
byCall?.delete(callId);
return context;
}
function cloneMcpCustomDataContextValue(value) {
try {
return structuredClone(value);
}
catch {
return value;
}
}
//# sourceMappingURL=mcp.mjs.map