LibreChat/packages/data-provider/src/mcp.ts
Per Weijnitz 19446cb864
feat: initTimeout for Slow Starting MCP Servers (#6383)
* feat: make mcp server connect timeout configurable with initTimeout

* style: add missing semicolon to connection.ts
2025-03-19 01:47:02 -04:00

111 lines
3 KiB
TypeScript

import { z } from 'zod';
import { extractEnvVariable } from './utils';
const BaseOptionsSchema = z.object({
iconPath: z.string().optional(),
timeout: z.number().optional(),
initTimeout: z.number().optional(),
});
export const StdioOptionsSchema = BaseOptionsSchema.extend({
type: z.literal('stdio').optional(),
/**
* The executable to run to start the server.
*/
command: z.string(),
/**
* Command line arguments to pass to the executable.
*/
args: z.array(z.string()),
/**
* The environment to use when spawning the process.
*
* If not specified, the result of getDefaultEnvironment() will be used.
* Environment variables can be referenced using ${VAR_NAME} syntax.
*/
env: z
.record(z.string(), z.string())
.optional()
.transform((env) => {
if (!env) {
return env;
}
const processedEnv: Record<string, string> = {};
for (const [key, value] of Object.entries(env)) {
processedEnv[key] = extractEnvVariable(value);
}
return processedEnv;
}),
/**
* How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.
*
* @type {import('node:child_process').IOType | import('node:stream').Stream | number}
*
* The default is "inherit", meaning messages to stderr will be printed to the parent process's stderr.
*/
stderr: z.any().optional(),
});
export const WebSocketOptionsSchema = BaseOptionsSchema.extend({
type: z.literal('websocket').optional(),
url: z
.string()
.url()
.refine(
(val) => {
const protocol = new URL(val).protocol;
return protocol === 'ws:' || protocol === 'wss:';
},
{
message: 'WebSocket URL must start with ws:// or wss://',
},
),
});
export const SSEOptionsSchema = BaseOptionsSchema.extend({
type: z.literal('sse').optional(),
url: z
.string()
.url()
.refine(
(val) => {
const protocol = new URL(val).protocol;
return protocol !== 'ws:' && protocol !== 'wss:';
},
{
message: 'SSE URL must not start with ws:// or wss://',
},
),
});
export const MCPOptionsSchema = z.union([
StdioOptionsSchema,
WebSocketOptionsSchema,
SSEOptionsSchema,
]);
export const MCPServersSchema = z.record(z.string(), MCPOptionsSchema);
export type MCPOptions = z.infer<typeof MCPOptionsSchema>;
/**
* Recursively processes an object to replace environment variables in string values
* @param {MCPOptions} obj - The object to process
* @returns {MCPOptions} - The processed object with environment variables replaced
*/
export function processMCPEnv(obj: MCPOptions): MCPOptions {
if (obj === null || obj === undefined) {
return obj;
}
if ('env' in obj && obj.env) {
const processedEnv: Record<string, string> = {};
for (const [key, value] of Object.entries(obj.env)) {
processedEnv[key] = extractEnvVariable(value);
}
obj.env = processedEnv;
}
return obj;
}