@langchain/mcp-adapters
Version:
LangChain.js adapters for Model Context Protocol (MCP)
416 lines (415 loc) • 16.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.clientConfigSchema = exports.connectionSchema = exports.streamableHttpConnectionSchema = exports.streamableHttpReconnectSchema = exports.stdioConnectionSchema = exports.stdioRestartSchema = exports.baseConfigSchema = exports.oAuthClientProviderSchema = exports.outputHandlingSchema = exports.callToolResultContentTypeSchema = void 0;
exports._resolveDetailedOutputHandling = _resolveDetailedOutputHandling;
exports._resolveAndApplyOverrideHandlingOverrides = _resolveAndApplyOverrideHandlingOverrides;
/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
const zod_1 = require("zod");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
function isZodObject(schema) {
return (typeof schema === "object" &&
schema !== null &&
"_def" in schema &&
schema._def.typeName === "ZodObject");
}
function isZodLiteral(schema) {
return (typeof schema === "object" &&
schema !== null &&
"_def" in schema &&
schema._def.typeName === "ZodLiteral");
}
/**
* Zod schema for an individual content item within a CallToolResult.
*/
const callToolResultContentSchema = types_js_1.CallToolResultSchema.shape.content._def.innerType.element;
const callToolResultContentTypes = callToolResultContentSchema.options.map((option) => {
if (isZodObject(option) &&
"type" in option.shape &&
isZodLiteral(option.shape.type)) {
return option.shape.type.value;
}
throw new Error("Internal error: Invalid option found in CallToolResultContentSchema's union. Expected ZodObject with ZodLiteral 'type'.");
});
const callToolResultContentTypeZodLiterals = callToolResultContentTypes.map((t) => zod_1.z.literal(t));
/**
* Zod schema for the 'type' field of a CallToolResultContent item.
* This will be a union of literals like "text", "image", "audio", and "resource".
*/
exports.callToolResultContentTypeSchema = zod_1.z.union(callToolResultContentTypeZodLiterals);
const outputTypesUnion = zod_1.z.union([
zod_1.z
.literal("content")
.describe("Put tool output into the ToolMessage.content array"),
zod_1.z
.literal("artifact")
.describe("Put tool output into the ToolMessage.artifact array"),
]);
const detailedOutputHandlingSchema = zod_1.z.object(Object.fromEntries(callToolResultContentTypes.map((contentType) => [
contentType,
zod_1.z
.union([
zod_1.z
.literal("content")
.describe(`Put all ${contentType} tool output into the ToolMessage.content array`),
zod_1.z
.literal("artifact")
.describe(`Put all ${contentType} tool output into the ToolMessage.artifact array`),
])
.describe(`Where to place ${contentType} tool output in the LangChain ToolMessage`)
.optional(),
])));
exports.outputHandlingSchema = zod_1.z
.union([outputTypesUnion, detailedOutputHandlingSchema])
.describe("Defines where to place each tool output type in the LangChain ToolMessage.\n\n" +
"Items in the `content` field will be used as input context for the LLM, while the artifact field is\n" +
"used for capturing tool output that won't be shown to the model, to be used in some later workflow\n" +
"step.\n\n" +
"For example, imagine that you have a SQL query tool that can return huge result sets. Rather than\n" +
"sending these large outputs directly to the model, perhaps you want the model to be able to inspect\n" +
"the output in a code execution environment. In this case, you would set the output handling for the\n" +
"`resource` type to `artifact` (it's default value), and then upon initialization of your code\n" +
"execution environment, you would look through your message history for `ToolMessage`s with the\n" +
"`artifact` field set to `resource`, and use the `content` field during initialization of the\n" +
"environment.");
/**
* Zod schema for validating OAuthClientProvider interface
* Since OAuthClientProvider has methods, we create a custom validator
*/
exports.oAuthClientProviderSchema = zod_1.z.custom((val) => {
if (!val || typeof val !== "object")
return false;
// Check required properties and methods exist
const requiredMethods = [
"redirectUrl",
"clientMetadata",
"clientInformation",
"tokens",
"saveTokens",
];
// redirectUrl can be a string, URL, or getter returning string/URL
if (!("redirectUrl" in val))
return false;
// clientMetadata can be an object or getter returning an object
if (!("clientMetadata" in val))
return false;
// Check that required methods exist (they can be functions or getters)
for (const method of requiredMethods) {
if (!(method in val))
return false;
}
return true;
}, {
message: "Must be a valid OAuthClientProvider implementation with required properties: redirectUrl, clientMetadata, clientInformation, tokens, saveTokens",
});
exports.baseConfigSchema = zod_1.z.object({
/**
* Defines where to place each tool output type in the LangChain ToolMessage.
*
* Can be set to `content` or `artifact` to send all tool output into the ToolMessage.content or
* ToolMessage.artifact array, respectively, or you can assign an object that maps each content type
* to `content` or `artifact`.
*
* @default {
* "text": "content",
* "image": "content",
* "audio": "content",
* "resource": "artifact"
* }
*
* Items in the `content` field will be used as input context for the LLM, while the artifact field is
* used for capturing tool output that won't be shown to the model, to be used in some later workflow
* step.
*
* For example, imagine that you have a SQL query tool that can return huge result sets. Rather than
* sending these large outputs directly to the model, perhaps you want the model to be able to inspect
* the output in a code execution environment. In this case, you would set the output handling for the
* `resource` type to `artifact` (its default value), and then upon initialization of your code
* execution environment, you would look through your message history for `ToolMessage`s with the
* `artifact` field set to `resource`, and use the `content` field during initialization of the
* environment.
*/
outputHandling: exports.outputHandlingSchema.optional(),
/**
* Default timeout in milliseconds for tool execution. Must be greater than 0.
* If not specified, tools will use their own configured timeout values.
*/
defaultToolTimeout: zod_1.z.number().min(1).optional(),
});
/**
* Stdio transport restart configuration
*/
exports.stdioRestartSchema = zod_1.z
.object({
/**
* Whether to automatically restart the process if it exits
*/
enabled: zod_1.z
.boolean()
.describe("Whether to automatically restart the process if it exits")
.optional(),
/**
* Maximum number of restart attempts
*/
maxAttempts: zod_1.z
.number()
.describe("The maximum number of restart attempts")
.optional(),
/**
* Delay in milliseconds between restart attempts
*/
delayMs: zod_1.z
.number()
.describe("The delay in milliseconds between restart attempts")
.optional(),
})
.describe("Configuration for stdio transport restart");
/**
* Stdio transport connection
*/
exports.stdioConnectionSchema = zod_1.z
.object({
/**
* Optional transport type, inferred from the structure of the config if not provided. Included
* for compatibility with common MCP client config file formats.
*/
transport: zod_1.z.literal("stdio").optional(),
/**
* Optional transport type, inferred from the structure of the config if not provided. Included
* for compatibility with common MCP client config file formats.
*/
type: zod_1.z.literal("stdio").optional(),
/**
* The executable to run the server (e.g. `node`, `npx`, etc)
*/
command: zod_1.z.string().describe("The executable to run the server"),
/**
* Array of command line arguments to pass to the executable
*/
args: zod_1.z
.array(zod_1.z.string())
.describe("Command line arguments to pass to the executable"),
/**
* Environment variables to set when spawning the process.
*/
env: zod_1.z
.record(zod_1.z.string())
.describe("The environment to use when spawning the process")
.optional(),
/**
* The encoding to use when reading from the process
*/
encoding: zod_1.z
.string()
.describe("The encoding to use when reading from the process")
.optional(),
/**
* How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`
*
* The default is "inherit", meaning messages to stderr will be printed to the parent process's stderr.
*
* @default "inherit"
*/
stderr: zod_1.z
.union([
zod_1.z.literal("overlapped"),
zod_1.z.literal("pipe"),
zod_1.z.literal("ignore"),
zod_1.z.literal("inherit"),
])
.describe("How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`")
.optional()
.default("inherit"),
/**
* The working directory to use when spawning the process.
*/
cwd: zod_1.z
.string()
.describe("The working directory to use when spawning the process")
.optional(),
/**
* Additional restart settings
*/
restart: exports.stdioRestartSchema.optional(),
})
.and(exports.baseConfigSchema)
.describe("Configuration for stdio transport connection");
/**
* Streamable HTTP transport reconnection configuration
*/
exports.streamableHttpReconnectSchema = zod_1.z
.object({
/**
* Whether to automatically reconnect if the connection is lost
*/
enabled: zod_1.z
.boolean()
.describe("Whether to automatically reconnect if the connection is lost")
.optional(),
/**
* Maximum number of reconnection attempts
*/
maxAttempts: zod_1.z
.number()
.describe("The maximum number of reconnection attempts")
.optional(),
/**
* Delay in milliseconds between reconnection attempts
*/
delayMs: zod_1.z
.number()
.describe("The delay in milliseconds between reconnection attempts")
.optional(),
})
.describe("Configuration for streamable HTTP transport reconnection");
/**
* Streamable HTTP transport connection
*/
exports.streamableHttpConnectionSchema = zod_1.z
.object({
/**
* Optional transport type, inferred from the structure of the config. If "sse", will not attempt
* to connect using streamable HTTP.
*/
transport: zod_1.z.union([zod_1.z.literal("http"), zod_1.z.literal("sse")]).optional(),
/**
* Optional transport type, inferred from the structure of the config. If "sse", will not attempt
* to connect using streamable HTTP.
*/
type: zod_1.z.union([zod_1.z.literal("http"), zod_1.z.literal("sse")]).optional(),
/**
* The URL to connect to
*/
url: zod_1.z.string().url(),
/**
* Additional headers to send with the request, useful for authentication
*/
headers: zod_1.z.record(zod_1.z.string()).optional(),
/**
* OAuth client provider for automatic authentication handling.
* When provided, the transport will automatically handle token refresh,
* 401 error retries, and OAuth 2.0 flows according to RFC 6750.
* This is the recommended approach for authentication instead of manual headers.
*/
authProvider: exports.oAuthClientProviderSchema.optional(),
/**
* Additional reconnection settings.
*/
reconnect: exports.streamableHttpReconnectSchema.optional(),
/**
* Whether to automatically fallback to SSE if Streamable HTTP is not available or not supported
*
* @default true
*/
automaticSSEFallback: zod_1.z.boolean().optional().default(true),
})
.and(exports.baseConfigSchema)
.describe("Configuration for streamable HTTP transport connection");
/**
* Create combined schema for all transport connection types
*/
exports.connectionSchema = zod_1.z
.union([exports.stdioConnectionSchema, exports.streamableHttpConnectionSchema])
.describe("Configuration for a single MCP server");
/**
* {@link MultiServerMCPClient} configuration
*/
exports.clientConfigSchema = zod_1.z
.object({
/**
* A map of server names to their configuration
*/
mcpServers: zod_1.z
.record(exports.connectionSchema)
.describe("A map of server names to their configuration"),
/**
* Whether to throw an error if a tool fails to load
*
* @default true
*/
throwOnLoadError: zod_1.z
.boolean()
.describe("Whether to throw an error if a tool fails to load")
.optional()
.default(true),
/**
* Whether to prefix tool names with the server name. Prefixes are separated by double
* underscores (example: `calculator_server_1__add`).
*
* @default true
*/
prefixToolNameWithServerName: zod_1.z
.boolean()
.describe("Whether to prefix tool names with the server name")
.optional()
.default(false),
/**
* An additional prefix to add to the tool name Prefixes are separated by double underscores
* (example: `mcp__add`).
*
* @default "mcp"
*/
additionalToolNamePrefix: zod_1.z
.string()
.describe("An additional prefix to add to the tool name")
.optional()
.default(""),
/**
* If true, the tool will use LangChain's standard multimodal content blocks for tools that output
* image or audio content, and embedded resources will be converted to `StandardFileBlock` objects.
* When `false`, all artifacts are left in their MCP format, but embedded resources will be
* converted to `StandardFileBlock` objects if {@link ClientConfig#outputHandling} causes embedded resources to
* be treated as content, as otherwise ChatModel providers will not be able to interpret them.
*
* @default false
*/
useStandardContentBlocks: zod_1.z
.boolean()
.describe("If true, the tool will use LangChain's standard multimodal content blocks for tools that output\n" +
"image or audio content. When true, embedded resources will be converted to `StandardFileBlock`\n" +
"objects. When `false`, all artifacts are left in their MCP format, but embedded resources will\n" +
"be converted to `StandardFileBlock` objects if `outputHandling` causes embedded resources to be\n" +
"treated as content, as otherwise ChatModel providers will not be able to interpret them.")
.optional()
.default(false),
})
.and(exports.baseConfigSchema)
.describe("Configuration for the MCP client");
/**
* Helper function that expands a string literal OutputHandling to an object with all content types.
* Used when applying server-level overrides to the top-level config.
*
* @internal
*/
function _resolveDetailedOutputHandling(outputHandling, applyDefaults = false) {
if (outputHandling == null) {
return {};
}
if (typeof outputHandling === "string") {
return Object.fromEntries(callToolResultContentTypes.map((contentType) => [
contentType,
outputHandling,
]));
}
const resolved = {};
for (const contentType of callToolResultContentTypes) {
if (outputHandling[contentType] || applyDefaults) {
resolved[contentType] =
outputHandling[contentType] ??
(contentType === "resource" ? "artifact" : "content");
}
}
return resolved;
}
/**
* Given a base {@link OutputHandling}, apply any overrides from the override {@link OutputHandling}.
*
* @internal
*/
function _resolveAndApplyOverrideHandlingOverrides(base, override) {
const expandedBase = _resolveDetailedOutputHandling(base);
const expandedOverride = _resolveDetailedOutputHandling(override);
return {
...expandedBase,
...expandedOverride,
};
}