feat: Add Dynamic User Field Placeholder Support in MCP Variables (#7825)

* chore: linting in mcp.spec.ts

* chore: linting in mcp.ts

* feat(mcp): support dynamic user field placeholders in MCP environment variables

- Added user object handling in MCP options, allowing for dynamic user field processing in environment variables, headers, and URLs.
- Updated `processMCPEnv` to utilize user fields for more flexible configurations.

* chore: update backend review workflow to include unit tests for @librechat/data-schemas
This commit is contained in:
Danny Avila 2025-06-10 22:20:41 -04:00 committed by GitHub
parent c2a18f61b4
commit cdf42b3a03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 357 additions and 39 deletions

View file

@ -1,6 +1,6 @@
import { CallToolResultSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import type { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
import type { JsonSchemaType, MCPOptions } from 'librechat-data-provider';
import type { JsonSchemaType, MCPOptions, TUser } from 'librechat-data-provider';
import type { Logger } from 'winston';
import type * as t from './types';
import { formatToolContent } from './parsers';
@ -8,7 +8,7 @@ import { MCPConnection } from './connection';
import { CONSTANTS } from './enum';
export interface CallToolOptions extends RequestOptions {
userId?: string;
user?: TUser;
}
export class MCPManager {
@ -21,7 +21,7 @@ export class MCPManager {
private userLastActivity: Map<string, number> = new Map();
private readonly USER_CONNECTION_IDLE_TIMEOUT = 15 * 60 * 1000; // 15 minutes (TODO: make configurable)
private mcpConfigs: t.MCPServers = {};
private processMCPEnv?: (obj: MCPOptions, userId?: string) => MCPOptions; // Store the processing function
private processMCPEnv?: (obj: MCPOptions, user?: TUser) => MCPOptions; // Store the processing function
/** Store MCP server instructions */
private serverInstructions: Map<string, string> = new Map();
private logger: Logger;
@ -219,7 +219,12 @@ export class MCPManager {
}
/** Gets or creates a connection for a specific user */
public async getUserConnection(userId: string, serverName: string): Promise<MCPConnection> {
public async getUserConnection(serverName: string, user: TUser): Promise<MCPConnection> {
const userId = user.id;
if (!userId) {
throw new McpError(ErrorCode.InvalidRequest, `[MCP] User object missing id property`);
}
const userServerMap = this.userConnections.get(userId);
let connection = userServerMap?.get(serverName);
const now = Date.now();
@ -267,7 +272,7 @@ export class MCPManager {
}
if (this.processMCPEnv) {
config = { ...(this.processMCPEnv(config, userId) ?? {}) };
config = { ...(this.processMCPEnv(config, user) ?? {}) };
}
connection = new MCPConnection(serverName, config, this.logger, userId);
@ -462,14 +467,15 @@ export class MCPManager {
options?: CallToolOptions;
}): Promise<t.FormattedToolResponse> {
let connection: MCPConnection | undefined;
const { userId, ...callOptions } = options ?? {};
const { user, ...callOptions } = options ?? {};
const userId = user?.id;
const logPrefix = userId ? `[MCP][User: ${userId}][${serverName}]` : `[MCP][${serverName}]`;
try {
if (userId) {
if (userId && user) {
this.updateUserLastActivity(userId);
// Get or create user-specific connection
connection = await this.getUserConnection(userId, serverName);
connection = await this.getUserConnection(serverName, user);
} else {
// Use app-level connection
connection = this.connections.get(serverName);