UNPKG

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
"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; } };