@microfox/tool-kit
Version:
A central tool kit for parsing Open Apis into MCP servers with Oauth Handling and HITL
1,461 lines (1,453 loc) • 50.5 kB
JavaScript
// 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
import { z as z2 } from "zod";
import { CryptoVault as CryptoVault2 } from "@microfox/crypto-sdk";
// src/parsing/jsonzod.ts
import { z } from "zod";
function convertOpenApiSchemaToZod(schema) {
if (!schema.type) {
return z.any();
}
switch (schema.type) {
case "string":
let stringSchema = z.string();
if (schema.format === "date-time") {
stringSchema = z.string().datetime();
}
if (schema.description) {
stringSchema = stringSchema.describe(schema.description);
}
return stringSchema;
case "number":
case "integer":
let numberSchema = schema.type === "integer" ? z.number().int() : z.number();
if (schema.description) {
numberSchema = numberSchema.describe(schema.description);
}
return numberSchema;
case "boolean":
let booleanSchema = z.boolean();
if (schema.description) {
booleanSchema = booleanSchema.describe(schema.description);
}
return booleanSchema;
case "array":
let itemSchema;
if (!schema.items) {
itemSchema = z.object({}).passthrough();
} else if (Array.isArray(schema.items)) {
if (schema.items.length === 0) {
itemSchema = z.object({}).passthrough();
} else {
const firstItemSchema = schema.items[0];
if (!firstItemSchema) {
itemSchema = z.object({}).passthrough();
} else {
itemSchema = convertOpenApiSchemaToZod(firstItemSchema);
}
}
} else {
itemSchema = convertOpenApiSchemaToZod(schema.items);
}
let arraySchema = 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 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 = z.object(shape);
if (schema.description) {
objectSchema = objectSchema.describe(schema.description);
}
return objectSchema;
default:
return 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
import { CryptoVault } from "@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 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 CryptoVault2({
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 = z2.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
};
})
);
const client = new OpenApiToolset(parsedOptions);
return await client.init();
} else {
const parsedSchema = await parseSchema(options.schema);
const client = new OpenApiToolset({
...options,
schema: parsedSchema
});
return await client.init();
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
throw new Error(`Failed to create OpenAPI client: ${errorMessage}`);
}
}
// src/types.ts
var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
var PAUSED_TOOL_CONTEXT = Symbol("paused_tool_context");
function isPausedToolContext(value) {
return typeof value === "object" && value !== null && "context" in value && value.context === PAUSED_TOOL_CONTEXT;
}
// src/mixed-client.ts
async function createMixedToolsClient(configs) {
const clients = {};
for (const config of configs) {
clients[config.id] = await createOpenApiToolset({
// The factory expects the schema directly.
schema: config.docData,
baseUrl: config.baseUrl
});
}
return {
async tools(options = {}) {
let finalToolSet = {};
for (const serverId in clients) {
const client = clients[serverId];
const serverToolResult = await client.tools({
disableAllExecutions: options.disableAllExecutions
});
const serverTools = serverToolResult.tools;
for (const toolName in serverTools) {
if (options.filter && !options.filter(too