UNPKG

@microfox/tool-kit

Version:

A central tool kit for parsing Open Apis into MCP servers with Oauth Handling and HITL

1,452 lines (1,443 loc) 52.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { HTTP_METHODS: () => HTTP_METHODS, OpenApiToolset: () => OpenApiToolset, PAUSED_TOOL_CONTEXT: () => PAUSED_TOOL_CONTEXT, constructHeaders: () => constructHeaders, createMixedToolsClient: () => createMixedToolsClient, createOpenApiToolset: () => createOpenApiToolset, getAuthOptions: () => getAuthOptions, isPausedToolContext: () => isPausedToolContext, parseSchema: () => parseSchema }); module.exports = __toCommonJS(index_exports); // src/utils.ts async function parseSchema(config) { let parsedSchema; if (typeof config === "string") { try { parsedSchema = JSON.parse(config); } catch (error) { try { const response = await fetch(config); if (!response.ok) { throw new Error(`Failed to fetch schema from URL: ${config}`); } parsedSchema = await response.json(); } catch (fetchError) { throw new Error( `Invalid schema JSON string and failed to fetch as URL: ${fetchError.message || String(fetchError)}` ); } } } else if (config && typeof config === "object") { if ("type" in config) { if (config.type === "object" && config.schema && typeof config.schema !== "string") { parsedSchema = config.schema; } else if (config.type === "text" && config.schema && typeof config.schema === "string") { try { parsedSchema = JSON.parse(config.schema); } catch (error) { throw new Error("Invalid schema JSON string"); } } else if (config.type === "url" && config.url) { try { const response = await fetch(config.url); if (!response.ok) { throw new Error(`Failed to fetch schema from URL: ${config.url}`); } parsedSchema = await response.json(); } catch (error) { throw new Error( `Failed to fetch schema from URL: ${error.message || String(error)}` ); } } else { throw new Error( "Invalid schema configuration: missing required properties for the specified type" ); } } else { parsedSchema = config; } } else { throw new Error("Invalid schema configuration"); } return parsedSchema; } // src/client/OpenAPiMcp.ts var import_zod2 = require("zod"); var import_crypto_sdk2 = require("@microfox/crypto-sdk"); // src/parsing/jsonzod.ts var import_zod = require("zod"); function convertOpenApiSchemaToZod(schema) { if (!schema.type) { return import_zod.z.any(); } switch (schema.type) { case "string": let stringSchema = import_zod.z.string(); if (schema.format === "date-time") { stringSchema = import_zod.z.string().datetime(); } if (schema.description) { stringSchema = stringSchema.describe(schema.description); } return stringSchema; case "number": case "integer": let numberSchema = schema.type === "integer" ? import_zod.z.number().int() : import_zod.z.number(); if (schema.description) { numberSchema = numberSchema.describe(schema.description); } return numberSchema; case "boolean": let booleanSchema = import_zod.z.boolean(); if (schema.description) { booleanSchema = booleanSchema.describe(schema.description); } return booleanSchema; case "array": let itemSchema; if (!schema.items) { itemSchema = import_zod.z.object({}).passthrough(); } else if (Array.isArray(schema.items)) { if (schema.items.length === 0) { itemSchema = import_zod.z.object({}).passthrough(); } else { const firstItemSchema = schema.items[0]; if (!firstItemSchema) { itemSchema = import_zod.z.object({}).passthrough(); } else { itemSchema = convertOpenApiSchemaToZod(firstItemSchema); } } } else { itemSchema = convertOpenApiSchemaToZod(schema.items); } let arraySchema = import_zod.z.array(itemSchema); if (schema.description) { arraySchema = arraySchema.describe(schema.description); } return arraySchema; case "object": if (!schema.properties || Object.keys(schema.properties).length === 0) { return import_zod.z.object({}).passthrough(); } const shape = {}; for (const [key, propSchema] of Object.entries(schema.properties)) { const propZodSchema = convertOpenApiSchemaToZod(propSchema); const isRequired = schema.required && schema.required.includes(key); if (isRequired) { shape[key] = propZodSchema; } else { shape[key] = propZodSchema.optional(); } } let objectSchema = import_zod.z.object(shape); if (schema.description) { objectSchema = objectSchema.describe(schema.description); } return objectSchema; default: return import_zod.z.any(); } } // src/parsing/argumentHelpers.ts function addPropertiesToBody(schema, additionalSchema) { if (!additionalSchema.properties || Object.keys(additionalSchema.properties).length === 0) { return; } if (!schema.properties) { schema.properties = {}; } let bodySchema = schema.properties.body; if (!bodySchema) { schema.properties.body = { type: "object", properties: additionalSchema.properties, ...additionalSchema.required && { required: additionalSchema.required } }; return; } if (bodySchema.type === "object") { if (!bodySchema.properties) { bodySchema.properties = {}; } bodySchema.properties = { ...bodySchema.properties, ...additionalSchema.properties }; if (additionalSchema.required) { if (!bodySchema.required) { bodySchema.required = []; } bodySchema.required = [ .../* @__PURE__ */ new Set([...bodySchema.required, ...additionalSchema.required]) ]; } return; } if (bodySchema.type === "array" && bodySchema.items) { const itemsToModify = Array.isArray(bodySchema.items) ? bodySchema.items[0] : bodySchema.items; if (itemsToModify && itemsToModify.type === "object") { if (!itemsToModify.properties) { itemsToModify.properties = {}; } itemsToModify.properties = { ...itemsToModify.properties, ...additionalSchema.properties }; if (additionalSchema.required) { if (!itemsToModify.required) { itemsToModify.required = []; } itemsToModify.required = [ .../* @__PURE__ */ new Set([...itemsToModify.required, ...additionalSchema.required]) ]; } } } } // src/client/headers.ts var import_crypto_sdk = require("@microfox/crypto-sdk"); function findOperationById(schema, operationId) { for (const [path, methods] of Object.entries(schema.paths)) { for (const [method, operation] of Object.entries(methods)) { if (operation.operationId === operationId) { return operation; } } } for (const [path, methods] of Object.entries(schema.paths)) { for (const [method, operation] of Object.entries(methods)) { const generatedId = generateOperationId(path, method, operation); if (generatedId === operationId) { return operation; } } } return null; } function generateOperationId(path, method, operation) { if (operation.operationId) { return operation.operationId; } const pathPart = path.replace(/^\/|\/$/g, "").replace(/[^a-zA-Z0-9]/g, "_").replace(/\/\{([^}]+)\}/g, "_by_$1").replace(/\//g, "_"); return `${method.toLowerCase()}_${pathPart}`; } function getAuthOptions(schema, operation) { var _a, _b, _c, _d, _e, _f, _g, _h, _i; let operationDetails; if (typeof operation === "string") { const foundOperation = findOperationById(schema, operation); if (!foundOperation) { throw new Error(`Operation "${operation}" not found in schema`); } operationDetails = foundOperation; } else { operationDetails = operation; } let packages = []; let customSecrets = []; let authConfig = (_d = (_c = (_a = operationDetails == null ? void 0 : operationDetails.security) == null ? void 0 : _a[0]) != null ? _c : (_b = schema == null ? void 0 : schema.security) == null ? void 0 : _b[0]) != null ? _d : {}; for (const [key, value] of Object.entries(authConfig)) { if (key === "x-microfox-packages") { packages = value == null ? void 0 : value.map((a) => { return { packageName: a, packageConstructor: [] }; }); } else { const customSecret = ((_g = (_f = (_e = schema == null ? void 0 : schema.components) == null ? void 0 : _e.securitySchemes) == null ? void 0 : _f[key]) == null ? void 0 : _g.type) === "apiKey" ? (_i = (_h = schema == null ? void 0 : schema.components) == null ? void 0 : _h.securitySchemes) == null ? void 0 : _i[key] : null; if (customSecret) { customSecrets.push({ key: customSecret == null ? void 0 : customSecret.name, description: customSecret == null ? void 0 : customSecret.description, required: true, type: customSecret == null ? void 0 : customSecret.type, format: customSecret == null ? void 0 : customSecret.in, enum: [], default: "" }); } } } return { packages, customSecrets }; } function constructHeaders(auth) { var _a; const headers = {}; if (!(auth == null ? void 0 : auth.encryptionKey)) { return headers; } try { const cryptoVault = new import_crypto_sdk.CryptoVault({ key: auth.encryptionKey, keyFormat: "base64", encryptionAlgo: "aes-256-gcm", hashAlgo: "sha256", outputEncoding: "base64url" }); if ((_a = auth.variables) == null ? void 0 : _a.length) { const variables = auth.variables.reduce( (acc, variable) => { acc[variable.key] = variable.value; return acc; }, {} ); if (Object.keys(variables).length > 0) { headers["x-auth-secrets"] = cryptoVault.encrypt( JSON.stringify(variables) ); } } } catch (error) { } return headers; } // src/client/OpenAPiMcp.ts var OpenApiMCP = class _OpenApiMCP { constructor(options) { this.initialized = false; this.schemaCache = {}; this.toolExecutions = {}; var _a, _b, _c, _d; this.schema = options.schema; this.baseUrl = options.baseUrl || ""; this.defaultHeaders = options.headers || {}; this.presetBodyFields = options.presetBodyFields || {}; this.onError = options.onError; this.operationMap = /* @__PURE__ */ new Map(); this.name = options.name || "openapi-tools-client"; this.mcp_version = ((_b = (_a = options == null ? void 0 : options.schema) == null ? void 0 : _a.info) == null ? void 0 : _b.mcpVersion) || "1.0.0"; this.auth = options.auth; this.getAuth = options.getAuth; this.cleanAuth = options.cleanAuth; this.getHumanIntervention = options.getHumanIntervention; this.getAdditionalArgs = options.getAdditionalArgs; if ((_c = options.auth) == null ? void 0 : _c.encryptionKey) { this.cryptoVault = new import_crypto_sdk2.CryptoVault({ key: ((_d = options.auth) == null ? void 0 : _d.encryptionKey) || "", keyFormat: "base64", encryptionAlgo: "aes-256-gcm", hashAlgo: "sha256", outputEncoding: "base64url" }); } } /** * Get client name */ getName() { return this.name; } /** * Initialize the client */ async init() { var _a; if (this.initialized) { return this; } try { if (!this.baseUrl && this.schema.servers && this.schema.servers.length > 0) { const serverUrl = (_a = this.schema.servers[0]) == null ? void 0 : _a.url; if (serverUrl) { this.baseUrl = serverUrl; } } this.setupOperationMap(); this.initialized = true; return this; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error during initialization"; if (this.onError && error instanceof Error) { this.onError(error); } throw new Error(`Failed to initialize OpenAPI client: ${errorMessage}`); } } cleanUpMethodSchema(methodSchema) { var _a, _b, _c, _d, _e, _f; const cleanedMethodSchema = JSON.parse(JSON.stringify(methodSchema)); if ((this == null ? void 0 : this.mcp_version) === "1.0.1" || (this == null ? void 0 : this.mcp_version) === "1.0.2") { const jsonContent = (_b = (_a = cleanedMethodSchema.requestBody) == null ? void 0 : _a.content) == null ? void 0 : _b["application/json"]; if (!((_f = (_e = (_d = (_c = jsonContent == null ? void 0 : jsonContent.schema) == null ? void 0 : _c.properties) == null ? void 0 : _d.body) == null ? void 0 : _e.properties) == null ? void 0 : _f.arguments)) { return cleanedMethodSchema; } const argumentsSchema = jsonContent.schema.properties.body.properties.arguments; let newProperties; if (argumentsSchema.type === "object" && argumentsSchema.properties) { newProperties = argumentsSchema.properties; } else if (argumentsSchema.type === "array" && argumentsSchema.items && Array.isArray(argumentsSchema.items)) { const items = argumentsSchema.items; newProperties = items.reduce( (acc, arg, index) => { acc[`arg_${index}`] = arg; return acc; }, {} ); } if (newProperties) { jsonContent.schema.properties = newProperties; } } return cleanedMethodSchema; } setupOperationMap() { for (const [path, methods] of Object.entries(this.schema.paths)) { for (const [method, pathOperation] of Object.entries( methods )) { const operation = pathOperation; const operationId = this.generateOperationId(path, method, operation); this.operationMap.set(operationId, { ...operation, path, method: method.toUpperCase() }); } } } generateOperationId(path, method, operation) { if (operation.operationId) { return operation.operationId; } const pathPart = path.replace(/^\/|\/$/g, "").replace(/[^a-zA-Z0-9]/g, "_").replace(/\/\{([^}]+)\}/g, "_by_$1").replace(/\//g, "_"); return `${method.toLowerCase()}_${pathPart}`; } static _generateToolName(operation, id) { let toolName = operation.name || operation.operationId || id; toolName = toolName.replace(/[^a-zA-Z0-9_]/g, ""); if (toolName.length > 128) { toolName = toolName.substring(0, 128); } if (!toolName) { toolName = id.replace(/[^a-zA-Z0-9_]/g, ""); if (toolName.length > 128) { toolName = toolName.substring(0, 128); } } return toolName; } structureBodyForRequest(body, operation, finalAuth) { if ((this == null ? void 0 : this.mcp_version) === "1.0.1") { return { body: { arguments: Array.isArray(body) ? Object.values(body) : body, auth: { ...this.presetBodyFields.auth || {}, ...finalAuth }, packageName: this.presetBodyFields.packageName } }; } else if ((this == null ? void 0 : this.mcp_version) === "1.0.2") { return { body: { arguments: Array.isArray(body) ? Object.values(body) : body, packageName: this.presetBodyFields.packageName } }; } return body; } structureHeaders(auth) { return constructHeaders(auth); } prepareRequest(operation, args, authHeaders) { var _a, _b, _c; let url = `${this.baseUrl}${operation.path}`; const headers = { ...this.defaultHeaders, "Content-Type": "application/json", ...authHeaders }; if (args.path) { Object.entries(args.path).forEach(([name, value]) => { if (value !== void 0) { url = url.replace(`{${name}}`, encodeURIComponent(String(value))); } }); } if (args.query) { const queryString = Object.entries(args.query).map(([name, value]) => { if (value !== void 0) { return `${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`; } return null; }).filter(Boolean).join("&"); if (queryString) { url += `?${queryString}`; } } if (args.headers) { Object.entries(args.headers).forEach(([name, value]) => { if (value !== void 0) { headers[name] = String(value); } }); } let body; if (["POST", "PUT", "PATCH"].includes(operation.method) && ((_c = (_b = (_a = operation.requestBody) == null ? void 0 : _a.content) == null ? void 0 : _b["application/json"]) == null ? void 0 : _c.schema)) { body = JSON.stringify(args.body); } return { url, headers, body }; } async makeRequest(options) { const { url, method, headers, body, signal } = options; try { const response = await fetch(url, { method, headers, body, signal }); const contentType = response.headers.get("content-type") || ""; let data; if (contentType.includes("application/json")) { data = await response.json(); } else if (contentType.includes("image/")) { data = await response.blob(); } else { data = await response.text(); } return { data, contentType, status: response.status }; } catch (error) { if (error instanceof Error && error.name === "AbortError") { throw error; } if (this.onError && error instanceof Error) { this.onError(error); } throw new Error( error instanceof Error ? error.message : "An unexpected error occurred" ); } } /** * List all available API operations from the OpenAPI schema * Operations are individual API endpoints defined in the OpenAPI schema */ async listOperations() { if (!this.initialized) { throw new Error("Client not initialized. Call init() first."); } const operations = []; for (const [id, operation] of this.operationMap.entries()) { operations.push({ id, summary: operation.summary, description: operation.description, parameters: operation.parameters, requestBody: operation.requestBody, path: operation.path, method: operation.method }); } return { operations }; } /** * Extracts system prompts from the OpenAPI schema. * @returns An object containing the global prompt and a map of operation-specific prompts. */ getSystemPrompts() { var _a, _b, _c; if (!this.initialized) { throw new Error("Client not initialized. Call init() first."); } const info = this.schema.info; const global = (_a = info == null ? void 0 : info.ai) == null ? void 0 : _a.systemPrompt; const operations = /* @__PURE__ */ new Map(); for (const [id, operation] of this.operationMap.entries()) { const op = operation; if ((_b = op == null ? void 0 : op.ai) == null ? void 0 : _b.systemPrompt) { const cleanedOperation = this.cleanUpMethodSchema( operation ); const toolName = _OpenApiMCP._generateToolName(cleanedOperation, id); operations.set(toolName, (_c = op == null ? void 0 : op.ai) == null ? void 0 : _c.systemPrompt); } } return { global, operations }; } getUiMap() { var _a, _b; const globalUi = (_a = this.schema.info) == null ? void 0 : _a["x-ui"]; const operationUis = /* @__PURE__ */ new Map(); const info = this.schema.info; const global = (_b = info == null ? void 0 : info.ai) == null ? void 0 : _b.systemPrompt; const operations = /* @__PURE__ */ new Map(); for (const [id, operation] of this.operationMap.entries()) { const op = operation; if (op == null ? void 0 : op.ui) { const cleanedOperation = this.cleanUpMethodSchema( operation ); const toolName = _OpenApiMCP._generateToolName(cleanedOperation, id); operationUis.set(toolName, op == null ? void 0 : op.ui); } } return { global: globalUi, operations: operationUis }; } /** * Call a specific API operation from the OpenAPI schema */ async callOperation(id, args = {}, options, auth) { if (!this.initialized) { throw new Error("Client not initialized. Call init() first."); } const operation = this.operationMap.get(id); if (!operation) { throw new Error( `Operation "${id}" not found. Available operations: ${Array.from( this.operationMap.keys() ).join(", ")}` ); } const pathArgs = args.path || {}; const queryArgs = args.query || {}; const headerArgs = args.headers || {}; let bodyArgs = args.body || {}; const knownParams = /* @__PURE__ */ new Set(["path", "query", "headers", "body"]); const extraArgs = Object.fromEntries( Object.entries(args).filter(([key]) => !knownParams.has(key)) ); if (Object.keys(extraArgs).length > 0) { bodyArgs = { ...bodyArgs, ...extraArgs }; } const structuredBody = this.structureBodyForRequest( bodyArgs, operation, auth ); const requestArgs = { path: pathArgs, query: queryArgs, headers: headerArgs, body: structuredBody }; const authHeaders = this.structureHeaders(auth); const { url, headers, body } = this.prepareRequest( operation, requestArgs, authHeaders ); return this.makeRequest({ url, method: operation.method, headers, body, signal: options == null ? void 0 : options.abortSignal }); } /** * Convert operation to JSON Schema */ convertOperationToJsonSchema(operation) { var _a, _b, _c; const rootSchema = { type: "object", properties: {}, required: [], $defs: this.convertComponentsToJsonSchema() }; const paramGroups = { path: { schema: { type: "object", properties: {}, required: [] }, params: [] }, query: { schema: { type: "object", properties: {}, required: [] }, params: [] }, header: { schema: { type: "object", properties: {}, required: [] }, params: [] } }; if (operation.parameters) { for (const param of operation.parameters) { const group = paramGroups[param.in]; if (group) { group.params.push(param); } } } for (const groupName of ["path", "query", "header"]) { const group = paramGroups[groupName]; if (group.params.length > 0) { for (const param of group.params) { if (param.schema) { const paramSchema = this.convertOpenApiSchemaToJsonSchema( param.schema, /* @__PURE__ */ new Set() ); if (param.description) { paramSchema.description = param.description; } group.schema.properties[param.name] = paramSchema; if (param.required) { group.schema.required.push(param.name); } } } rootSchema.properties[groupName] = group.schema; if (groupName === "path" && group.params.length > 0) { rootSchema.required.push("path"); } } } if ((_c = (_b = (_a = operation.requestBody) == null ? void 0 : _a.content) == null ? void 0 : _b["application/json"]) == null ? void 0 : _c.schema) { let bodySchema = this.convertOpenApiSchemaToJsonSchema( operation.requestBody.content["application/json"].schema, /* @__PURE__ */ new Set() ); if (bodySchema.type === "object" && bodySchema.properties && Object.keys(bodySchema.properties).length === 1 && bodySchema.properties.body) { bodySchema = bodySchema.properties.body; } rootSchema.properties.body = bodySchema; if (operation.requestBody.required) { rootSchema.required.push("body"); } } return rootSchema; } /** * Convert components to JSON Schema */ convertComponentsToJsonSchema() { const components = this.schema.components || {}; const schema = {}; for (const [key, value] of Object.entries(components.schemas || {})) { schema[key] = this.convertOpenApiSchemaToJsonSchema(value, /* @__PURE__ */ new Set()); } return schema; } /** * Resolve a $ref reference to its schema in the openApiSpec */ internalResolveRef(ref, resolvedRefs) { if (!ref.startsWith("#/")) { return null; } if (resolvedRefs.has(ref)) { return null; } const parts = ref.replace(/^#\//, "").split("/"); let current = this.schema; for (const part of parts) { current = current[part]; if (!current) return null; } resolvedRefs.add(ref); return current; } /** * Convert OpenAPI schema to JSON Schema */ convertOpenApiSchemaToJsonSchema(schema, resolvedRefs, resolveRefs = false) { if (schema.$ref) { const ref = schema.$ref; if (!resolveRefs) { if (ref.startsWith("#/components/schemas/")) { return { $ref: ref.replace(/^#\/components\/schemas\//, "#/$defs/"), ...schema.description ? { description: schema.description } : {} }; } } const refSchema = { $ref: ref }; if (schema.description) { refSchema.description = schema.description; } if (this.schemaCache[ref]) { return this.schemaCache[ref]; } const resolved = this.internalResolveRef(ref, resolvedRefs); if (!resolved) { return { $ref: ref.replace(/^#\/components\/schemas\//, "#/$defs/"), description: schema.description || "" }; } else { const converted = this.convertOpenApiSchemaToJsonSchema( resolved, resolvedRefs, resolveRefs ); this.schemaCache[ref] = converted; return converted; } } const result = {}; if (schema.type) { result.type = schema.type; } if (schema.format === "binary") { result.format = "uri-reference"; const binaryDesc = "absolute paths to local files"; result.description = schema.description ? `${schema.description} (${binaryDesc})` : binaryDesc; } else { if (schema.format) { result.format = schema.format; } if (schema.description) { result.description = schema.description; } } if (schema.enum) { result.enum = schema.enum; } if (schema.default !== void 0) { result.default = schema.default; } if (schema.type === "object") { result.type = "object"; if (schema.properties) { result.properties = {}; for (const [name, propSchema] of Object.entries(schema.properties)) { result.properties[name] = this.convertOpenApiSchemaToJsonSchema( propSchema, resolvedRefs, resolveRefs ); } } if (schema.required) { result.required = schema.required; } if (schema.additionalProperties === true || schema.additionalProperties === void 0) { result.additionalProperties = true; } else if (schema.additionalProperties && typeof schema.additionalProperties === "object") { result.additionalProperties = this.convertOpenApiSchemaToJsonSchema( schema.additionalProperties, resolvedRefs, resolveRefs ); } else { result.additionalProperties = false; } } if (schema.type === "array") { result.type = "array"; if (schema.items) { if (Array.isArray(schema.items)) { result.items = schema.items.map( (itemSchema) => this.convertOpenApiSchemaToJsonSchema( itemSchema, resolvedRefs, resolveRefs ) ); } else { result.items = this.convertOpenApiSchemaToJsonSchema( schema.items, resolvedRefs, resolveRefs ); } } else { result.items = { type: "object", additionalProperties: true }; } } if (schema.oneOf) { result.oneOf = schema.oneOf.map( (s) => this.convertOpenApiSchemaToJsonSchema(s, resolvedRefs, resolveRefs) ); } if (schema.anyOf) { result.anyOf = schema.anyOf.map( (s) => this.convertOpenApiSchemaToJsonSchema(s, resolvedRefs, resolveRefs) ); } if (schema.allOf) { result.allOf = schema.allOf.map( (s) => this.convertOpenApiSchemaToJsonSchema(s, resolvedRefs, resolveRefs) ); } return result; } //Each tool call must have a corresponding tool result. If you do not add a tool result, all subsequent generations will fail //bypass execute function process it, and return void, to put a temp stop // format a dataStreamPart with tool_call, and speciall toolName, & some state args, that help // get answer and push this part jsut above the preivously paused part, making it look like, // when asked for pramod, what his best time to meet is, pramod answered in {slack} message that he wants to connect on tuesday. aslong with all result data // add/modify args & permit the execute function to be called, // human layer will tell what tool to call, to get permission / input but will not execute in here, as it does not know the client secrets /** * Returns a set of tools generated from the OpenAPI schema */ async tools({ schemas = "automatic", validateParameters = false, disabledExecutions = [], includeDisabled = false, disableAllExecutions = false, auth: toolAuth, getAuth: toolGetAuth, cleanAuth: toolCleanAuth, getHumanIntervention, getAdditionalArgs } = {}) { var _a; if (!this.initialized) { throw new Error("Client not initialized. Call init() first."); } const tools = {}; const toolExecutions = {}; const metadata = {}; const uiMaps = {}; const { operations } = await this.listOperations(); for (const [id, operation] of this.operationMap.entries()) { if (schemas !== "automatic" && !schemas[id]) { continue; } if (((_a = operation == null ? void 0 : operation.ai) == null ? void 0 : _a.disableTool) && !includeDisabled) { continue; } const cleanedOperation = this.cleanUpMethodSchema( operation ); if (!cleanedOperation.path || !cleanedOperation.method) continue; const toolName = _OpenApiMCP._generateToolName(cleanedOperation, id); const isDisabled = disableAllExecutions || Array.isArray(disabledExecutions) && disabledExecutions.includes(toolName); const jsonSchema = this.convertOperationToJsonSchema(cleanedOperation); const getAdditionalArgsFn = getAdditionalArgs || this.getAdditionalArgs; let additionalArgsSchema; if (getAdditionalArgsFn) { const additionalArgsZod = await getAdditionalArgsFn({ toolName, clientName: this.name, summary: cleanedOperation.summary }); if (additionalArgsZod) { additionalArgsSchema = import_zod2.z.toJSONSchema(additionalArgsZod); } } const finalJsonSchema = JSON.parse(JSON.stringify(jsonSchema)); if (additionalArgsSchema) { addPropertiesToBody(finalJsonSchema, additionalArgsSchema); } const zodSchema = convertOpenApiSchemaToZod(finalJsonSchema); const toolMetaData = { toolName, clientName: this.name, summary: cleanedOperation.summary, description: cleanedOperation.description || cleanedOperation.summary || `${cleanedOperation.method} ${cleanedOperation.path}`, jsonSchema: finalJsonSchema, humanLayer: { required: isDisabled } }; const executeFn = async (args, toolOptions) => { var _a2; let finalAuth; const { packages, customSecrets } = getAuthOptions(this.schema, cleanedOperation); if (!finalAuth) { if (toolGetAuth) { finalAuth = await toolGetAuth({ packages, customSecrets }); } else if (toolAuth) { finalAuth = toolAuth; } else if (this.getAuth) { finalAuth = await this.getAuth({ packages, customSecrets }); } else if (this.auth) { finalAuth = this.auth; } } const cleanAuthFn = toolCleanAuth || this.cleanAuth; const { encryptionKey, ...cleanedAuth } = finalAuth || {}; if (toolOptions == null ? void 0 : toolOptions.auth) { finalAuth = { ...toolOptions == null ? void 0 : toolOptions.auth, encryptionKey: ((_a2 = toolOptions == null ? void 0 : toolOptions.auth) == null ? void 0 : _a2.encryptionKey) || (finalAuth == null ? void 0 : finalAuth.encryptionKey) }; } if (getHumanIntervention) { const authForHuman = cleanAuthFn && finalAuth ? await cleanAuthFn(finalAuth) : cleanedAuth; const decision = await getHumanIntervention({ toolName, generatedArgs: args, mcpConfig: cleanedOperation, toolCallId: (toolOptions == null ? void 0 : toolOptions.toolCallId) || "", auth: authForHuman }); if (decision == null ? void 0 : decision.shouldPause) { return { _humanIntervention: true, toolCallId: toolOptions == null ? void 0 : toolOptions.toolCallId, args: decision.args, metadata: toolMetaData, auth: authForHuman }; } } if (disableAllExecutions) { const authForHuman = cleanAuthFn && finalAuth ? await cleanAuthFn(finalAuth) : cleanedAuth; return { _humanIntervention: true, toolCallId: toolOptions == null ? void 0 : toolOptions.toolCallId, args, metadata: toolMetaData, auth: authForHuman }; } const argsForCall = { ...args }; if (additionalArgsSchema == null ? void 0 : additionalArgsSchema.properties) { if (argsForCall.body && typeof argsForCall.body === "object") { for (const key of Object.keys(additionalArgsSchema.properties)) { delete argsForCall.body[key]; } } } return this.callOperation(id, argsForCall, toolOptions, finalAuth); }; tools[toolName] = { type: "function", toolName, clientName: this.name, inputSchema: zodSchema, summary: cleanedOperation.summary, description: cleanedOperation.description || cleanedOperation.summary || `${cleanedOperation.method} ${cleanedOperation.path}`, execute: executeFn //isDisabled ? undefined : executeFn, }; toolExecutions[toolName] = executeFn; metadata[toolName] = toolMetaData; } const { operations: uiMapsData } = this.getUiMap(); for (const [toolName, uiMap] of uiMapsData.entries()) { uiMaps[toolName] = uiMap; } return { tools, executions: toolExecutions, metadata, uiMaps }; } /** * Get the execute callback for a specific tool */ getToolExecution(id) { return this.toolExecutions[id]; } resolveAuthProvider(operationId) { var _a, _b, _c; const operationDetails = this.operationMap.get(operationId); const schema = this.schema; let authDirective = (_a = operationDetails == null ? void 0 : operationDetails.auth) != null ? _a : schema.auth; if (!authDirective) { return void 0; } if (typeof authDirective === "object") { return authDirective; } if (typeof authDirective === "string") { return (_c = (_b = schema.components) == null ? void 0 : _b["x-auth-providers"]) == null ? void 0 : _c[authDirective]; } return void 0; } resolveJsonPath(obj, path) { if (!path.startsWith("$.")) return void 0; const keys = path.substring(2).split("."); let current = obj; for (const key of keys) { if (current === null || current === void 0) return void 0; current = current[key]; } return current; } processResponseMapping(mapping, responseData) { if (typeof mapping === "string") { if (mapping.startsWith("'") && mapping.endsWith("'")) { return mapping.substring(1, mapping.length - 1); } if (mapping.startsWith("$.")) { return this.resolveJsonPath(responseData, mapping); } return mapping; } if (Array.isArray(mapping)) { return mapping.map( (item) => this.processResponseMapping(item, responseData) ); } if (typeof mapping === "object" && mapping !== null) { const newObj = {}; for (const key in mapping) { newObj[key] = this.processResponseMapping(mapping[key], responseData); } return newObj; } return mapping; } }; // src/client/Toolset.ts var FAKE_HUMAN_INTERACTION_TOOL_NAME = "FAKE_HUMAN_INTERACTION"; var OpenApiToolset = class { constructor(options) { this.clients = []; this.initialized = false; this.pendingTools = /* @__PURE__ */ new Map(); const optionsArray = Array.isArray(options) ? options : [options]; optionsArray.forEach((opt, index) => { var _a, _b, _c, _d; const schemName = (_b = (_a = opt.schema.info) == null ? void 0 : _a.title) == null ? void 0 : _b.toLowerCase().replace(/[^a-zA-Z0-9]/g, "_"); this.clients.push( new OpenApiMCP({ ...opt, baseUrl: typeof opt.schema === "object" && "url" in opt.schema ? opt.schema.url : (_c = opt.baseUrl) != null ? _c : "", schema: typeof opt.schema === "string" ? JSON.parse(opt.schema) : opt.schema, name: (_d = opt.name) != null ? _d : `${(schemName == null ? void 0 : schemName.length) > 16 ? schemName.substring(0, 16) : schemName}`, getHumanIntervention: opt.getHumanIntervention }) ); }); this.options = options; } /** * Add a tool to the pending map */ addPendingTool(context) { this.pendingTools.set(context.originalToolCallId, context); } /** * Find and remove a pending tool from the map */ consumePendingTool(toolCallId) { const context = this.pendingTools.get(toolCallId); if (context) { this.pendingTools.delete(toolCallId); } return context; } /** * Initialize all clients */ async init() { if (this.initialized) { return this; } try { await Promise.all(this.clients.map((client) => client.init())); this.initialized = true; return this; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error during initialization"; throw new Error(`Failed to initialize OpenAPI clients: ${errorMessage}`); } } /** * List all available API operations from all OpenAPI schemas */ async listOperations() { if (!this.initialized) { throw new Error("Clients not initialized. Call init() first."); } const allOperationsResults = await Promise.all( this.clients.map((client) => client.listOperations()) ); const operations = allOperationsResults.flatMap((result, index) => { const client = this.clients[index]; if (!client) return []; return result.operations.map((op) => ({ ...op, // Add client name to ID to avoid conflicts id: `${client.getName()}AsTool${op.id}` })); }); return { operations }; } /** * Call a specific API operation from any client */ async callOperation(id, args = {}, options) { if (!this.initialized) { throw new Error("Clients not initialized. Call init() first."); } const [clientName, operationId] = id.split("AsTool"); if (!clientName || !operationId) { throw new Error( `Invalid operation ID format: ${id}. Expected format: "clientNameAsTooloperationId"` ); } const client = this.clients.find((c) => c.getName() === clientName); if (!client) { throw new Error(`Client "${clientName}" not found.`); } return client.callOperation(operationId, args, options); } /** * Generates a combined system prompt from all OpenAPI schemas. */ generateSystemPrompt() { if (!this.initialized) { throw new Error("Clients not initialized. Call init() first."); } const allPrompts = []; this.clients.forEach((client) => { const { global, operations } = client.getSystemPrompts(); if (global) { allPrompts.push(global); } const clientName = client.getName(); operations.forEach((prompt, toolName) => { const namespacedKey = `${clientName}AsTool${toolName}`; allPrompts.push(`[${namespacedKey}]: ${prompt}`); }); }); return allPrompts.filter((p) => p && p.trim()).join("\n\n"); } /** * Returns a set of tools generated from all OpenAPI schemas */ async tools({ schemas = "automatic", validateParameters = false, disabledExecutions = [], disableAllExecutions = false, auth, getAuth, getHumanIntervention } = {}) { if (!this.initialized) { throw new Error("Clients not initialized. Call init() first."); } const allToolsResults = await Promise.all( this.clients.map( (client) => client.tools({ schemas, validateParameters, // Pass validation option to each client disabledExecutions, disableAllExecutions, auth, getAuth, getHumanIntervention }) ) ); const combinedTools = { tools: {}, executions: {}, metadata: {}, uiMaps: {} }; allToolsResults.forEach((toolSet, index) => { const client = this.clients[index]; if (!client) return; const clientName = client.getName(); Object.entries(toolSet.tools).forEach(([key, tool]) => { const namespacedKey = `${clientName}AsTool${key}`; combinedTools.tools[namespacedKey] = { ...tool, clientName, _id: namespacedKey // Update name to include namespace }; if (toolSet.executions[key] && toolSet.metadata[key]) { combinedTools.executions[namespacedKey] = toolSet.executions[key]; combinedTools.metadata[namespacedKey] = toolSet.metadata[key]; } }); if (toolSet.uiMaps) { Object.entries(toolSet.uiMaps).forEach(([key, uiMap]) => { const namespacedKey = `${clientName}AsTool${key}`; if (combinedTools.uiMaps) { combinedTools.uiMaps[namespacedKey] = uiMap; } }); } }); return combinedTools; } /** * Parses incoming messages for HITL responses, resumes the original tool, * and returns a cleaned message history. * @param messages The full message history. * @returns A promise that resolves to the processed message history. */ async processHitlToolResult(messages, options) { const lastMessage = messages[messages.length - 1]; if (!lastMessage) return messages; const parts = lastMessage.parts; if (!parts) return messages; const thisClientTools = await this.tools(); const processedParts = await Promise.all( parts.map(async (part) => { var _a, _b; if (!part.type.startsWith("tool-")) return part; const toolName = part.type.replace("tool-", ""); if (!(toolName in thisClientTools.executions)) return part; let result; let _input = part.input; if (part.state === "output-available" && "approved" in part.output) { const correspondingCall = thisClientTools.executions[toolName]; const uiMap = (_a = thisClientTools.uiMaps) == null ? void 0 : _a[toolName]; if (!correspondingCall) { result = "Error: No execute function found on tool"; } else if (part.output.approved) { let _auth = part.output.auth; if (options.inserAuthVariables && _auth) { _auth = await options.inserAuthVariables(_auth); } if (part.output.mutatedInput && Object.keys(part.output.mutatedInput).length > 0) { _input = part.output.mutatedInput; } result = await correspondingCall(_input, { toolCallId: part.toolCallId, messages, ..._auth ? { auth: _auth } : {} }); if (options.mutateOutput) { result = await options.mutateOutput(result, { part, uiMapper: uiMap }); } if (result.data.task_id) { result.backgroundTask = { taskId: result.data.task_id, startedAt: (/* @__PURE__ */ new Date()).toISOString(), status: "pending", message: `You task is started, I will notify you when it is completed.` }; } result.toolSummary = (_b = thisClientTools.metadata[toolName]) == null ? void 0 : _b.summary; } else { result = "Error: User denied access to tool execution"; } } if (options.dataStream && result) { options.dataStream.write({ type: "tool-output-available", toolCallId: part.toolCallId, output: result //providerMetadata: {}, }); } return { ...part, input: _input, state: "output-available", output: result }; }) ); return [ ...messages.slice(0, -1), { ...lastMessage, parts: processedParts } ]; } isHitlStep() { return async (s) => { var _a; try { const lastStep = s.steps[s.steps.length - 1]; if ((lastStep == null ? void 0 : lastStep.toolResults) && ((_a = lastStep == null ? void 0 : lastStep.toolResults) == null ? void 0 : _a.length) > 0) { const allToolResults = lastStep.toolResults; if (allToolResults.find( (t) => t && t.output && t.output._humanIntervention )) { return true; } } return false; } catch (error) { console.error("Error in isHitlStep:", error); return false; } }; } /** * Processes a stream from `streamText`, transforming paused tool markers * into `FAKE_HUMAN_INTERACTION` tool calls on the fly. * @param resultStream The stream from `streamText`. * @returns A new stream with HITL logic applied. */ stream(resultStream, dataStream) { const client = this; const [streamForProcessing, streamToReturn] = resultStream.tee(); (async () => { var _a, _b; const reader = streamForProcessing.getReader(); try { while (true) { const { done, value: part } = await reader.read(); if (done) { break; } if (part.type === "tool-result" && ((_a = part.toolResult.result) == null ? void 0 : _a._humanIntervention) === true) { const { toolCallId, result } = part.toolResult; const pendingTool = client.pendingTools.get(toolCallId); if (pendingTool && dataStream) { const interventionArgs = (_b = result.args) != null ? _b : {}; const fakeToolCallPart = { type: "tool-call", toolCallId: `${toolCallId}-human-intervention`, toolName: FAKE_HUMAN_INTERACTION_TOOL_NAME, args: { originalToolCallId: toolCallId, originalToolName: pendingTool.toolName, ...interventionArgs } }; dataStream.write(fakeToolCallPart); } } } } catch (error) { } })(); return streamToReturn; } isHumanLoop(part) { return part.toolName === FAKE_HUMAN_INTERACTION_TOOL_NAME && part.state === "call"; } ui(part) { if (!this.isHumanLoop(part)) { return null; } const { originalToolCallId, originalToolName, ...uiArgs } = part.input; return uiArgs; } }; async function createOpenApiToolset(options) { try { const isArray = Array.isArray(options); if (isArray) { const parsedOptions = await Promise.all( options.map(async (opt) => { const parsedSchema = await parseSchema(opt.schema); return { ...opt, schema: parsedSchema }; }) );