mcp-use
Version:
Opinionated MCP Framework for TypeScript (@modelcontextprotocol/sdk compatible) - Build MCP Agents, Clients and Servers with support for ChatGPT Apps, Code Mode, OAuth, Notifications, Sampling, Observability and more.
1,233 lines (1,227 loc) • 41 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
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/adapters/index.ts
var adapters_exports = {};
__export(adapters_exports, {
BaseAdapter: () => BaseAdapter,
LangChainAdapter: () => LangChainAdapter
});
module.exports = __toCommonJS(adapters_exports);
// src/logging.ts
var DEFAULT_LOGGER_NAME = "mcp-use";
function resolveLevel(env) {
const envValue = typeof process !== "undefined" && process.env ? env : void 0;
switch (envValue?.trim()) {
case "2":
return "debug";
case "1":
return "info";
default:
return "info";
}
}
__name(resolveLevel, "resolveLevel");
function formatArgs(args) {
if (args.length === 0) return "";
return args.map((arg) => {
if (typeof arg === "string") return arg;
try {
return JSON.stringify(arg);
} catch {
return String(arg);
}
}).join(" ");
}
__name(formatArgs, "formatArgs");
var SimpleConsoleLogger = class {
static {
__name(this, "SimpleConsoleLogger");
}
_level;
name;
format;
constructor(name = DEFAULT_LOGGER_NAME, level = "info", format = "minimal") {
this.name = name;
this._level = level;
this.format = format;
}
shouldLog(level) {
const levels = [
"error",
"warn",
"info",
"http",
"verbose",
"debug",
"silly"
];
const currentIndex = levels.indexOf(this._level);
const messageIndex = levels.indexOf(level);
return messageIndex <= currentIndex;
}
formatMessage(level, message, args) {
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
const extraArgs = formatArgs(args);
const fullMessage = extraArgs ? `${message} ${extraArgs}` : message;
switch (this.format) {
case "detailed":
return `${timestamp} [${this.name}] ${level.toUpperCase()}: ${fullMessage}`;
case "emoji": {
const emojiMap = {
error: "\u274C",
warn: "\u26A0\uFE0F",
info: "\u2139\uFE0F",
http: "\u{1F310}",
verbose: "\u{1F4DD}",
debug: "\u{1F50D}",
silly: "\u{1F92A}"
};
return `${timestamp} [${this.name}] ${emojiMap[level] || ""} ${level.toUpperCase()}: ${fullMessage}`;
}
case "minimal":
default:
return `${timestamp} [${this.name}] ${level}: ${fullMessage}`;
}
}
error(message, ...args) {
if (this.shouldLog("error")) {
console.error(this.formatMessage("error", message, args));
}
}
warn(message, ...args) {
if (this.shouldLog("warn")) {
console.warn(this.formatMessage("warn", message, args));
}
}
info(message, ...args) {
if (this.shouldLog("info")) {
console.info(this.formatMessage("info", message, args));
}
}
debug(message, ...args) {
if (this.shouldLog("debug")) {
console.debug(this.formatMessage("debug", message, args));
}
}
http(message, ...args) {
if (this.shouldLog("http")) {
console.log(this.formatMessage("http", message, args));
}
}
verbose(message, ...args) {
if (this.shouldLog("verbose")) {
console.log(this.formatMessage("verbose", message, args));
}
}
silly(message, ...args) {
if (this.shouldLog("silly")) {
console.log(this.formatMessage("silly", message, args));
}
}
get level() {
return this._level;
}
set level(newLevel) {
this._level = newLevel;
}
setFormat(format) {
this.format = format;
}
};
var Logger = class {
static {
__name(this, "Logger");
}
static instances = {};
static currentFormat = "minimal";
static get(name = DEFAULT_LOGGER_NAME) {
if (!this.instances[name]) {
const debugEnv = typeof process !== "undefined" && process.env?.DEBUG || void 0;
this.instances[name] = new SimpleConsoleLogger(
name,
resolveLevel(debugEnv),
this.currentFormat
);
}
return this.instances[name];
}
static configure(options = {}) {
const { level, format = "minimal" } = options;
const debugEnv = typeof process !== "undefined" && process.env?.DEBUG || void 0;
const resolvedLevel = level ?? resolveLevel(debugEnv);
this.currentFormat = format;
Object.values(this.instances).forEach((logger2) => {
logger2.level = resolvedLevel;
logger2.setFormat(format);
});
}
static setDebug(enabled) {
let level;
if (enabled === 2 || enabled === true) level = "debug";
else if (enabled === 1) level = "info";
else level = "info";
Object.values(this.instances).forEach((logger2) => {
logger2.level = level;
});
if (typeof process !== "undefined" && process.env) {
process.env.DEBUG = enabled ? enabled === true ? "2" : String(enabled) : "0";
}
}
static setFormat(format) {
this.currentFormat = format;
this.configure({ format });
}
};
var logger = Logger.get();
// src/adapters/base.ts
var BaseAdapter = class {
static {
__name(this, "BaseAdapter");
}
/**
* List of tool names that should not be available.
*/
disallowedTools;
/**
* Internal cache that maps a connector instance to the list of tools
* generated for it.
*/
connectorToolMap = /* @__PURE__ */ new Map();
constructor(disallowedTools) {
this.disallowedTools = disallowedTools ?? [];
}
/**
* Create tools from an MCPClient instance.
*
* This is the recommended way to create tools from an MCPClient, as it handles
* session creation and connector extraction automatically.
*
* @param client The MCPClient to extract tools from.
* @param disallowedTools Optional list of tool names to exclude.
* @returns A promise that resolves with a list of converted tools.
*/
static async createTools(client, disallowedTools) {
const adapter = new this(disallowedTools);
if (!client.activeSessions || Object.keys(client.activeSessions).length === 0) {
logger.info("No active sessions found, creating new ones...");
await client.createAllSessions();
}
const sessions = client.getAllActiveSessions();
const connectors = Object.values(sessions).map(
(session) => session.connector
);
return adapter.createToolsFromConnectors(connectors);
}
/**
* Dynamically load tools for a specific connector.
*
* @param connector The connector to load tools for.
* @returns The list of tools that were loaded in the target framework's format.
*/
async loadToolsForConnector(connector) {
if (this.connectorToolMap.has(connector)) {
const cached = this.connectorToolMap.get(connector);
logger.debug(`Returning ${cached.length} existing tools for connector`);
return cached;
}
const connectorTools = [];
const success = await this.ensureConnectorInitialized(connector);
if (!success) {
return [];
}
for (const tool of connector.tools) {
const converted = this.convertTool(tool, connector);
if (converted) {
connectorTools.push(converted);
}
}
this.connectorToolMap.set(connector, connectorTools);
logger.debug(
`Loaded ${connectorTools.length} new tools for connector: ${connectorTools.map((t) => t?.name ?? String(t)).join(", ")}`
);
return connectorTools;
}
/**
* Create tools from MCP tools in all provided connectors.
*
* @param connectors List of MCP connectors to create tools from.
* @returns A promise that resolves with all converted tools.
*/
async createToolsFromConnectors(connectors) {
const tools = [];
for (const connector of connectors) {
const connectorTools = await this.loadToolsForConnector(connector);
tools.push(...connectorTools);
}
logger.debug(`Available tools: ${tools.length}`);
return tools;
}
/**
* Dynamically load resources for a specific connector.
*
* @param connector The connector to load resources for.
* @returns The list of resources that were loaded in the target framework's format.
*/
async loadResourcesForConnector(connector) {
const connectorResources = [];
const success = await this.ensureConnectorInitialized(connector);
if (!success) {
return [];
}
try {
const resourcesResult = await connector.listAllResources();
const resources = resourcesResult?.resources || [];
if (this.convertResource) {
for (const resource of resources) {
const converted = this.convertResource(resource, connector);
if (converted) {
connectorResources.push(converted);
}
}
}
logger.debug(
`Loaded ${connectorResources.length} new resources for connector: ${connectorResources.map((r) => r?.name ?? String(r)).join(", ")}`
);
} catch (err) {
logger.warn(`Error loading resources for connector: ${err}`);
}
return connectorResources;
}
/**
* Dynamically load prompts for a specific connector.
*
* @param connector The connector to load prompts for.
* @returns The list of prompts that were loaded in the target framework's format.
*/
async loadPromptsForConnector(connector) {
const connectorPrompts = [];
const success = await this.ensureConnectorInitialized(connector);
if (!success) {
return [];
}
try {
const promptsResult = await connector.listPrompts();
const prompts = promptsResult?.prompts || [];
if (this.convertPrompt) {
for (const prompt of prompts) {
const converted = this.convertPrompt(prompt, connector);
if (converted) {
connectorPrompts.push(converted);
}
}
}
logger.debug(
`Loaded ${connectorPrompts.length} new prompts for connector: ${connectorPrompts.map((p) => p?.name ?? String(p)).join(", ")}`
);
} catch (err) {
logger.warn(`Error loading prompts for connector: ${err}`);
}
return connectorPrompts;
}
/**
* Create resources from MCP resources in all provided connectors.
*
* @param connectors List of MCP connectors to create resources from.
* @returns A promise that resolves with all converted resources.
*/
async createResourcesFromConnectors(connectors) {
const resources = [];
for (const connector of connectors) {
const connectorResources = await this.loadResourcesForConnector(connector);
resources.push(...connectorResources);
}
logger.debug(`Available resources: ${resources.length}`);
return resources;
}
/**
* Create prompts from MCP prompts in all provided connectors.
*
* @param connectors List of MCP connectors to create prompts from.
* @returns A promise that resolves with all converted prompts.
*/
async createPromptsFromConnectors(connectors) {
const prompts = [];
for (const connector of connectors) {
const connectorPrompts = await this.loadPromptsForConnector(connector);
prompts.push(...connectorPrompts);
}
logger.debug(`Available prompts: ${prompts.length}`);
return prompts;
}
/**
* Check if a connector is initialized and has tools.
*
* @param connector The connector to check.
* @returns True if the connector is initialized and has tools, false otherwise.
*/
checkConnectorInitialized(connector) {
return Boolean(connector.tools && connector.tools.length);
}
/**
* Ensure a connector is initialized.
*
* @param connector The connector to initialize.
* @returns True if initialization succeeded, false otherwise.
*/
async ensureConnectorInitialized(connector) {
if (!this.checkConnectorInitialized(connector)) {
logger.debug("Connector doesn't have tools, initializing it");
try {
await connector.initialize();
return true;
} catch (err) {
logger.error(`Error initializing connector: ${err}`);
return false;
}
}
return true;
}
};
// src/utils/json-schema-to-zod/JSONSchemaToZod.ts
var import_zod = require("zod");
var JSONSchemaToZod = class {
static {
__name(this, "JSONSchemaToZod");
}
/**
* Converts a JSON schema to a Zod schema.
*
* @param {JSONSchema} schema - The JSON schema.
* @returns {ZodSchema} - The Zod schema.
*/
static convert(schema) {
return this.parseSchema(schema);
}
/**
* Checks if data matches a condition schema.
*
* @param {JSONValue} data - The data to check.
* @param {JSONSchema} condition - The condition schema.
* @returns {boolean} - Whether the data matches the condition.
*/
static matchesCondition(data, condition) {
if (!condition.properties) {
return true;
}
if (typeof data !== "object" || data === null || Array.isArray(data)) {
return false;
}
const objectData = data;
for (const [key, propCondition] of Object.entries(condition.properties)) {
if (!(key in objectData)) {
if ("const" in propCondition) {
return false;
}
continue;
}
const value = objectData[key];
if ("const" in propCondition && value !== propCondition["const"]) {
return false;
}
if ("minimum" in propCondition && typeof value === "number" && value < propCondition["minimum"]) {
return false;
}
if ("maximum" in propCondition && typeof value === "number" && value > propCondition["maximum"]) {
return false;
}
}
return true;
}
/**
* Validates data against a conditional schema and adds issues to context if validation fails.
*
* @param {JSONValue} data - The data to validate.
* @param {JSONSchema} schema - The conditional schema.
* @param {z.RefinementCtx} ctx - The Zod refinement context.
*/
static validateConditionalSchema(data, schema, ctx) {
this.validateRequiredProperties(data, schema, ctx);
this.validatePropertyPatterns(data, schema, ctx);
this.validateNestedConditions(data, schema, ctx);
}
/**
* Validates that all required properties are present in the data.
*
* @param {JSONValue} data - The data to validate.
* @param {JSONSchema} schema - The schema containing required properties.
* @param {z.RefinementCtx} ctx - The Zod refinement context.
*/
static validateRequiredProperties(data, schema, ctx) {
if (!schema.required) {
return;
}
if (typeof data !== "object" || data === null) {
for (const requiredProp of schema.required) {
ctx.addIssue({
code: import_zod.z.ZodIssueCode.custom,
message: `Required property '${requiredProp}' is missing`,
path: [requiredProp]
});
}
return;
}
for (const requiredProp of schema.required) {
if (!(requiredProp in data)) {
ctx.addIssue({
code: import_zod.z.ZodIssueCode.custom,
message: `Required property '${requiredProp}' is missing`,
path: [requiredProp]
});
}
}
}
/**
* Validates property patterns for string properties.
*
* @param {JSONValue} data - The data to validate.
* @param {JSONSchema} schema - The schema containing property patterns.
* @param {z.RefinementCtx} ctx - The Zod refinement context.
*/
static validatePropertyPatterns(data, schema, ctx) {
if (!schema.properties) {
return;
}
if (typeof data !== "object" || data === null) {
return;
}
if (Array.isArray(data)) {
return;
}
const objectData = data;
for (const [key, propSchema] of Object.entries(schema.properties)) {
if (!(key in objectData)) {
continue;
}
const value = objectData[key];
if (propSchema["pattern"] && typeof value === "string") {
const regex = new RegExp(propSchema["pattern"]);
if (!regex.test(value)) {
ctx.addIssue({
code: import_zod.z.ZodIssueCode.custom,
message: `String '${value}' does not match pattern '${propSchema["pattern"]}'`,
path: [key]
});
}
}
}
}
/**
* Validates nested if-then-else conditions.
*
* @param {JSONValue} data - The data to validate.
* @param {JSONSchema} schema - The schema containing if-then-else conditions.
* @param {z.RefinementCtx} ctx - The Zod refinement context.
*/
static validateNestedConditions(data, schema, ctx) {
if (!schema["if"] || !schema["then"]) {
return;
}
const matchesIf = this.matchesCondition(data, schema["if"]);
if (matchesIf) {
this.validateConditionalSchema(data, schema["then"], ctx);
} else if (schema["else"]) {
this.validateConditionalSchema(data, schema["else"], ctx);
}
}
/**
* Parses a JSON schema and returns the corresponding Zod schema.
* This is the main entry point for schema conversion.
*
* @param {JSONSchema} schema - The JSON schema.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseSchema(schema) {
if (Array.isArray(schema.type)) {
return this.handleTypeArray(schema);
}
if (schema.oneOf || schema.anyOf || schema.allOf) {
return this.parseCombinator(schema);
}
if (schema["if"] && schema["then"]) {
return this.parseObject(schema);
}
if (schema.properties && (!schema.type || schema.type === "object")) {
return this.parseObject(schema);
}
return this.handleSingleType(schema);
}
/**
* Handles schemas with an array of types.
*
* @param {JSONSchema} schema - The JSON schema with type array.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static handleTypeArray(schema) {
if (!Array.isArray(schema.type)) {
throw new Error("Expected schema.type to be an array");
}
if (schema.type.includes("null")) {
return this.handleNullableType(schema);
}
return this.createUnionFromTypes(schema.type, schema);
}
/**
* Handles nullable types by creating a nullable schema.
*
* @param {JSONSchema} schema - The JSON schema with nullable type.
* @returns {ZodTypeAny} - The nullable Zod schema.
*/
static handleNullableType(schema) {
if (!Array.isArray(schema.type)) {
throw new Error("Expected schema.type to be an array");
}
const nonNullSchema = { ...schema };
nonNullSchema.type = schema.type.filter((t) => t !== "null");
if (nonNullSchema.type.length === 1) {
const singleTypeSchema = this.handleSingleType({
...schema,
type: nonNullSchema.type[0]
});
return singleTypeSchema.nullable();
}
const unionSchema = this.parseSchema(nonNullSchema);
return unionSchema.nullable();
}
/**
* Creates a union type from an array of types.
*
* @param {string[]} types - Array of type strings.
* @param {JSONSchema} baseSchema - The base schema to apply to each type.
* @returns {ZodTypeAny} - The union Zod schema.
*/
static createUnionFromTypes(types, baseSchema) {
const schemas = types.map((type) => {
const singleTypeSchema = { ...baseSchema, type };
return this.parseSchema(singleTypeSchema);
});
return import_zod.z.union(schemas);
}
/**
* Handles schemas with a single type.
*
* @param {JSONSchema} schema - The JSON schema with single type.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static handleSingleType(schema) {
if (schema.type === void 0) {
if (schema.oneOf || schema.anyOf || schema.allOf) {
return this.parseCombinator(schema);
}
if (schema.properties) {
return this.parseObject(schema);
}
return import_zod.z.any();
}
switch (schema.type) {
case "string":
return this.parseString(schema);
case "number":
case "integer":
return this.parseNumberSchema(schema);
case "boolean":
return import_zod.z.boolean();
case "array":
return this.parseArray(schema);
case "object":
return this.parseObject(schema);
default:
throw new Error("Unsupported schema type");
}
}
/**
* Parses a number schema.
*
* @param {JSONSchema} schema - The JSON schema for a number.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseNumberSchema(schema) {
const numberSchema = import_zod.z.number();
let result = numberSchema;
result = this.applyNumberBounds(numberSchema, schema);
result = this.applyNumberMultipleOf(numberSchema, schema);
result = this.applyNumberEnum(numberSchema, schema);
result = this.applyIntegerConstraint(numberSchema, schema);
return result;
}
/**
* Applies bounds validation to a number schema.
*
* @param {z.ZodNumber} numberSchema - The base number schema.
* @param {JSONSchema} schema - The JSON schema with bounds.
* @returns {z.ZodNumber} - The updated schema with bounds validation.
*/
static applyNumberBounds(numberSchema, schema) {
let result = numberSchema;
if (schema["minimum"] !== void 0) {
result = schema["exclusiveMinimum"] ? result.gt(schema["minimum"]) : result.gte(schema["minimum"]);
}
if (schema["maximum"] !== void 0) {
result = schema["exclusiveMaximum"] ? result.lt(schema["maximum"]) : result.lte(schema["maximum"]);
}
return result;
}
/**
* Applies multipleOf validation to a number schema.
*
* @param {z.ZodNumber} numberSchema - The base number schema.
* @param {JSONSchema} schema - The JSON schema with multipleOf.
* @returns {z.ZodNumber} - The updated schema with multipleOf validation.
*/
static applyNumberMultipleOf(numberSchema, schema) {
if (schema["multipleOf"] === void 0) {
return numberSchema;
}
return numberSchema.refine((val) => val % schema["multipleOf"] === 0, {
message: `Number must be a multiple of ${schema["multipleOf"]}`
});
}
/**
* Applies enum validation to a number schema.
*
* @param {z.ZodNumber} numberSchema - The base number schema.
* @param {JSONSchema} schema - The JSON schema with enum.
* @returns {z.ZodNumber} - The updated schema with enum validation.
*/
static applyNumberEnum(numberSchema, schema) {
if (!schema.enum) {
return numberSchema;
}
const numberEnums = schema.enum.filter(
(val) => typeof val === "number"
);
if (numberEnums.length === 0) {
return numberSchema;
}
return numberSchema.refine((val) => numberEnums.includes(val), {
message: `Number must be one of: ${numberEnums.join(", ")}`
});
}
/**
* Applies integer constraint to a number schema if needed.
*
* @param {z.ZodNumber} numberSchema - The base number schema.
* @param {JSONSchema} schema - The JSON schema.
* @returns {z.ZodNumber} - The updated schema with integer validation if needed.
*/
static applyIntegerConstraint(numberSchema, schema) {
if (schema.type !== "integer") {
return numberSchema;
}
return numberSchema.refine((val) => Number.isInteger(val), {
message: "Number must be an integer"
});
}
/**
* Parses a string schema.
*
* @param {JSONSchema} schema - The JSON schema for a string.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseString(schema) {
const stringSchema = import_zod.z.string();
let result = stringSchema;
if (schema.format) {
return this.applyStringFormat(stringSchema, schema);
} else {
result = this.applyStringPattern(stringSchema, schema);
result = this.applyStringLength(stringSchema, schema);
result = this.applyStringEnum(stringSchema, schema);
}
return result;
}
/**
* Applies format validation to a string schema.
*
* @param {z.ZodString} stringSchema - The base string schema.
* @param {JSONSchema} schema - The JSON schema with format.
* @returns {ZodTypeAny} - The updated schema with format validation.
*/
static applyStringFormat(stringSchema, schema) {
if (!schema.format) {
return stringSchema;
}
switch (schema.format) {
case "email":
return stringSchema.email();
case "date-time":
return stringSchema.datetime();
case "uri":
return stringSchema.url();
case "uuid":
return stringSchema.uuid();
case "date":
return stringSchema.date();
default:
return stringSchema;
}
}
/**
* Applies pattern validation to a string schema.
*
* @param {z.ZodString} stringSchema - The base string schema.
* @param {JSONSchema} schema - The JSON schema with pattern.
* @returns {z.ZodString} - The updated schema with pattern validation.
*/
static applyStringPattern(stringSchema, schema) {
if (!schema["pattern"]) {
return stringSchema;
}
const regex = new RegExp(schema["pattern"]);
return stringSchema.regex(regex, {
message: `String must match pattern: ${schema["pattern"]}`
});
}
/**
* Applies length constraints to a string schema.
*
* @param {z.ZodString} stringSchema - The base string schema.
* @param {JSONSchema} schema - The JSON schema with length constraints.
* @returns {z.ZodString} - The updated schema with length validation.
*/
static applyStringLength(stringSchema, schema) {
const result = stringSchema;
if (schema["minLength"] !== void 0) {
stringSchema = stringSchema.min(schema["minLength"]);
}
if (schema["maxLength"] !== void 0) {
stringSchema = stringSchema.max(schema["maxLength"]);
}
return result;
}
/**
* Applies enum validation to a string schema.
*
* @param {z.ZodString} stringSchema - The base string schema.
* @param {JSONSchema} schema - The JSON schema with enum.
* @returns {ZodTypeAny} - The updated schema with enum validation.
*/
static applyStringEnum(stringSchema, schema) {
if (!schema.enum) {
return stringSchema;
}
return stringSchema.refine((val) => schema.enum?.includes(val), {
message: `Value must be one of: ${schema.enum?.join(", ")}`
});
}
/**
* Parses a JSON schema of type array and returns the corresponding Zod schema.
*
* @param {JSONSchema} schema - The JSON schema.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseArray(schema) {
if (Array.isArray(schema.items)) {
const tupleSchemas = schema.items.map((item) => this.parseSchema(item));
return import_zod.z.union(tupleSchemas);
}
const itemSchema = schema.items ? this.parseSchema(schema.items) : import_zod.z.any();
const arraySchema = import_zod.z.array(itemSchema);
let result = arraySchema;
result = this.applyArrayConstraints(arraySchema, schema);
return result;
}
/**
* Applies constraints to an array schema.
*
* @param {z.ZodArray<any>} arraySchema - The base array schema.
* @param {JSONSchema} schema - The JSON schema with array constraints.
* @returns {z.ZodTypeAny} - The updated array schema with constraints.
*/
static applyArrayConstraints(arraySchema, schema) {
if (schema["minItems"] !== void 0) {
arraySchema = arraySchema.min(schema["minItems"]);
}
if (schema["maxItems"] !== void 0) {
arraySchema = arraySchema.max(schema["maxItems"]);
}
if (schema["uniqueItems"]) {
return arraySchema.refine(
(items) => new Set(items).size === items.length,
{ message: "Array items must be unique" }
);
}
return arraySchema;
}
/**
* Parses an object schema.
*
* @param {JSONSchema} schema - The JSON schema for an object.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseObject(schema) {
if (schema["if"] && schema["then"]) {
return this.parseConditional(schema);
}
const shape = {};
this.processObjectProperties(schema, shape);
return this.processAdditionalProperties(schema, import_zod.z.object(shape));
}
/**
* Processes object properties and builds the shape object.
*
* @param {JSONSchema} schema - The JSON schema for an object.
* @param {Record<string, ZodTypeAny>} shape - The shape object to populate.
*/
static processObjectProperties(schema, shape) {
const required = new Set(schema.required || []);
if (!schema.properties) {
return;
}
for (const [key, propSchema] of Object.entries(schema.properties)) {
const zodSchema = this.parseSchema(propSchema);
shape[key] = required.has(key) ? zodSchema : zodSchema.optional();
}
}
/**
* Processes additionalProperties configuration.
*
* @param {JSONSchema} schema - The JSON schema for an object.
* @param {z.ZodObject<any, any>} objectSchema - The Zod object schema.
* @returns {z.ZodObject<any, any>} - The updated Zod object schema.
*/
static processAdditionalProperties(schema, objectSchema) {
if (schema.additionalProperties === true) {
return objectSchema.passthrough();
} else if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
const additionalPropSchema = this.parseSchema(
schema.additionalProperties
);
return objectSchema.catchall(additionalPropSchema);
} else {
return objectSchema.strict();
}
}
/**
* Parses a conditional schema with if-then-else.
*
* @param {JSONSchema} schema - The JSON schema with conditional validation.
* @returns {ZodTypeAny} - The conditional Zod schema.
*/
static parseConditional(schema) {
const zodObject = this.createBaseObjectSchema(schema);
const ifCondition = schema["if"];
const thenSchema = schema["then"];
const elseSchema = schema["else"];
return zodObject.superRefine((data, ctx) => {
const dataWithDefaults = this.applyDefaultValues(
data,
schema
);
if (this.matchesCondition(dataWithDefaults, ifCondition)) {
this.validateConditionalSchema(dataWithDefaults, thenSchema, ctx);
} else if (elseSchema) {
this.validateConditionalSchema(dataWithDefaults, elseSchema, ctx);
}
});
}
/**
* Creates a base object schema from the given JSON schema.
*
* @param {JSONSchema} schema - The JSON schema.
* @returns {z.ZodObject<any, any>} - The base Zod object schema.
*/
static createBaseObjectSchema(schema) {
const shape = {};
const required = new Set(schema.required || []);
for (const [key, value] of Object.entries(schema.properties || {})) {
const zodSchema = this.parseSchema(value);
shape[key] = required.has(key) ? zodSchema : zodSchema.optional();
}
const zodObject = import_zod.z.object(shape);
return this.processAdditionalProperties(schema, zodObject);
}
/**
* Applies default values from schema properties to data object.
*
* @param {JSONValue} data - The original data object.
* @param {JSONSchema} schema - The schema with default values.
* @returns {JSONValue} - The data object with defaults applied.
*/
static applyDefaultValues(data, schema) {
if (typeof data !== "object" || data === null) {
return data;
}
if (Array.isArray(data)) {
return data;
}
const objectData = data;
const dataWithDefaults = { ...objectData };
if (!schema.properties) {
return dataWithDefaults;
}
for (const [key, propSchema] of Object.entries(schema.properties)) {
if (!(key in dataWithDefaults) && "default" in propSchema) {
dataWithDefaults[key] = propSchema["default"];
}
}
return dataWithDefaults;
}
/**
* Parses a schema with combinators (oneOf, anyOf, allOf).
* Delegates to the appropriate combinator parser based on which combinator is present.
*
* @param {JSONSchema} schema - The JSON schema with combinators.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseCombinator(schema) {
if (schema.oneOf) {
return this.parseOneOf(schema.oneOf);
}
if (schema.anyOf) {
return this.parseAnyOf(schema.anyOf);
}
if (schema.allOf) {
return this.parseAllOf(schema.allOf);
}
throw new Error("Unsupported schema type");
}
/**
* Parses a oneOf combinator schema.
*
* @param {JSONSchema[]} schemas - Array of JSON schemas in the oneOf.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseOneOf(schemas) {
return this.createUnionFromSchemas(schemas);
}
/**
* Parses an anyOf combinator schema.
*
* @param {JSONSchema[]} schemas - Array of JSON schemas in the anyOf.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseAnyOf(schemas) {
return this.createUnionFromSchemas(schemas);
}
/**
* Creates a union from an array of schemas, handling special cases.
*
* @param {JSONSchema[]} schemas - Array of JSON schemas to create a union from.
* @returns {ZodTypeAny} - The union Zod schema.
*/
static createUnionFromSchemas(schemas) {
if (schemas.length === 0) {
return import_zod.z.any();
}
if (schemas.length === 1) {
return this.parseSchema(schemas[0]);
}
const zodSchemas = [];
for (const subSchema of schemas) {
if (subSchema.type === "null") {
zodSchemas.push(import_zod.z.null());
} else {
zodSchemas.push(this.parseSchema(subSchema));
}
}
if (zodSchemas.length >= 2) {
return import_zod.z.union(zodSchemas);
} else if (zodSchemas.length === 1) {
return zodSchemas[0];
}
return import_zod.z.any();
}
/**
* Parses an allOf combinator schema by merging all schemas.
*
* @param {JSONSchema[]} schemas - Array of JSON schemas in the allOf.
* @returns {ZodTypeAny} - The ZodTypeAny schema.
*/
static parseAllOf(schemas) {
if (schemas.length === 0) {
return import_zod.z.any();
}
if (schemas.length === 1) {
return this.parseSchema(schemas[0]);
}
const mergedSchema = schemas.reduce(
(acc, currentSchema) => this.mergeSchemas(acc, currentSchema)
);
return this.parseSchema(mergedSchema);
}
/**
* Merges two JSON schemas together.
*
* @param {JSONSchema} baseSchema - The base JSON schema.
* @param {JSONSchema} addSchema - The JSON schema to add.
* @returns {JSONSchema} - The merged JSON schema
*/
static mergeSchemas(baseSchema, addSchema) {
const merged = { ...baseSchema, ...addSchema };
if (baseSchema.properties && addSchema.properties) {
const mergedProperties = {
...baseSchema.properties,
...addSchema.properties
};
merged.properties = mergedProperties;
}
if (baseSchema.required && addSchema.required) {
const mergedRequired = [
.../* @__PURE__ */ new Set([...baseSchema.required, ...addSchema.required])
];
merged.required = mergedRequired;
}
return merged;
}
};
// src/adapters/langchain_adapter.ts
var import_tools = require("@langchain/core/tools");
var import_zod2 = require("zod");
function schemaToZod(schema) {
try {
return JSONSchemaToZod.convert(schema);
} catch (err) {
logger.warn(`Failed to convert JSON schema to Zod: ${err}`);
return import_zod2.z.any();
}
}
__name(schemaToZod, "schemaToZod");
var LangChainAdapter = class extends BaseAdapter {
static {
__name(this, "LangChainAdapter");
}
constructor(disallowedTools = []) {
super(disallowedTools);
}
/**
* Convert a single MCP tool specification into a LangChainJS structured tool.
*/
convertTool(mcpTool, connector) {
if (this.disallowedTools.includes(mcpTool.name)) {
return null;
}
const argsSchema = mcpTool.inputSchema ? schemaToZod(mcpTool.inputSchema) : import_zod2.z.object({}).optional();
const tool = new import_tools.DynamicStructuredTool({
name: mcpTool.name ?? "NO NAME",
description: mcpTool.description ?? "",
// Blank is acceptable but discouraged.
schema: argsSchema,
func: /* @__PURE__ */ __name(async (input) => {
logger.debug(
`MCP tool "${mcpTool.name}" received input: ${JSON.stringify(input)}`
);
try {
const result = await connector.callTool(
mcpTool.name,
input
);
return JSON.stringify(result);
} catch (err) {
logger.error(`Error executing MCP tool: ${err.message}`);
return `Error executing MCP tool: ${String(err)}`;
}
}, "func")
});
return tool;
}
/**
* Convert a single MCP resource into a LangChainJS structured tool.
* Each resource becomes an async tool that returns its content when called.
*/
convertResource(mcpResource, connector) {
const sanitizeName = /* @__PURE__ */ __name((name) => {
return name.replace(/[^A-Za-z0-9_]+/g, "_").toLowerCase().replace(/^_+|_+$/g, "");
}, "sanitizeName");
const resourceName = sanitizeName(
mcpResource.name || `resource_${mcpResource.uri}`
);
const resourceUri = mcpResource.uri;
const tool = new import_tools.DynamicStructuredTool({
name: resourceName,
description: mcpResource.description || `Return the content of the resource located at URI ${resourceUri}.`,
schema: import_zod2.z.object({}).optional(),
// Resources take no arguments
func: /* @__PURE__ */ __name(async () => {
logger.debug(`Resource tool: "${resourceName}" called`);
try {
const result = await connector.readResource(resourceUri);
if (result.contents && result.contents.length > 0) {
return result.contents.map((content) => {
if (typeof content === "string") {
return content;
}
if (content.text) {
return content.text;
}
if (content.uri) {
return content.uri;
}
return JSON.stringify(content);
}).join("\n");
}
return "Resource is empty or unavailable";
} catch (err) {
logger.error(`Error reading resource: ${err.message}`);
return `Error reading resource: ${String(err)}`;
}
}, "func")
});
return tool;
}
/**
* Convert a single MCP prompt into a LangChainJS structured tool.
* The resulting tool executes getPrompt on the connector with the prompt's name
* and the user-provided arguments (if any).
*/
convertPrompt(mcpPrompt, connector) {
let argsSchema = import_zod2.z.object({}).optional();
if (mcpPrompt.arguments && mcpPrompt.arguments.length > 0) {
const schemaFields = {};
for (const arg of mcpPrompt.arguments) {
const zodType = import_zod2.z.string();
if (arg.required !== false) {
schemaFields[arg.name] = zodType;
} else {
schemaFields[arg.name] = zodType.optional();
}
}
argsSchema = Object.keys(schemaFields).length > 0 ? import_zod2.z.object(schemaFields) : import_zod2.z.object({}).optional();
}
const tool = new import_tools.DynamicStructuredTool({
name: mcpPrompt.name,
description: mcpPrompt.description || "",
schema: argsSchema,
func: /* @__PURE__ */ __name(async (input) => {
logger.debug(
`Prompt tool: "${mcpPrompt.name}" called with args: ${JSON.stringify(input)}`
);
try {
const result = await connector.getPrompt(mcpPrompt.name, input);
if (result.messages && result.messages.length > 0) {
return result.messages.map((msg) => {
if (typeof msg === "string") {
return msg;
}
if (msg.content) {
return typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
}
return JSON.stringify(msg);
}).join("\n");
}
return "Prompt returned no messages";
} catch (err) {
logger.error(`Error getting prompt: ${err.message}`);
return `Error getting prompt: ${String(err)}`;
}
}, "func")
});
return tool;
}
};