mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-05 07:40:19 +01:00
* feat: Allow Credential Variables in Headers for DB-sourced MCP Servers - Removed the hasCustomUserVars check from ToolService.js, directly retrieving userMCPAuthMap. - Updated MCPConnectionFactory and related classes to include a dbSourced flag for better handling of database-sourced configurations. - Added integration tests to ensure proper behavior of dbSourced servers, verifying that sensitive placeholders are not resolved while allowing customUserVars. - Adjusted various MCP-related files to accommodate the new dbSourced logic, ensuring consistent handling across the codebase. * chore: MCPConnectionFactory Tests with Additional Flow Metadata for typing - Updated MCPConnectionFactory tests to include new fields in flowMetadata: serverUrl and state. - Enhanced mockFlowData in multiple test cases to reflect the updated structure, ensuring comprehensive coverage of the OAuth flow scenarios. - Added authorization_endpoint to metadata in the test setup for improved validation of the OAuth process. * refactor: Simplify MCPManager Configuration Handling - Removed unnecessary type assertions and streamlined the retrieval of server configuration in MCPManager. - Enhanced the handling of OAuth and database-sourced flags for improved clarity and efficiency. - Updated tests to reflect changes in user object structure and ensure proper processing of MCP environment variables. * refactor: Optimize User MCP Auth Map Retrieval in ToolService - Introduced conditional loading of userMCPAuthMap based on the presence of MCP-delimited tools, improving efficiency by avoiding unnecessary calls. - Updated the loadToolDefinitionsWrapper and loadAgentTools functions to reflect this change, enhancing overall performance and clarity. * test: Add userMCPAuthMap gating tests in ToolService - Introduced new tests to validate the logic for determining if MCP tools are present in the agent's tool list. - Implemented various scenarios to ensure accurate detection of MCP tools, including edge cases for empty, undefined, and null tool lists. - Enhanced clarity and coverage of the ToolService capability checking logic. * refactor: Enhance MCP Environment Variable Processing - Simplified the handling of the dbSourced parameter in the processMCPEnv function. - Introduced a failsafe mechanism to derive dbSourced from options if not explicitly provided, improving robustness and clarity in MCP environment variable processing. * refactor: Update Regex Patterns for Credential Placeholders in ServerConfigsDB - Modified regex patterns to include additional credential/env placeholders that should not be allowed in user-provided configurations. - Clarified comments to emphasize the security risks associated with credential exfiltration when MCP servers are shared between users. * chore: field order * refactor: Clean Up dbSourced Parameter Handling in processMCPEnv - Reintroduced the failsafe mechanism for deriving the dbSourced parameter from options, ensuring clarity and robustness in MCP environment variable processing. - Enhanced code readability by maintaining consistent comment structure. * refactor: Update MCPOptions Type to Include Optional dbId - Modified the processMCPEnv function to extend the MCPOptions type, allowing for an optional dbId property. - Simplified the logic for deriving the dbSourced parameter by directly checking the dbId property, enhancing code clarity and maintainability.
144 lines
5.1 KiB
TypeScript
144 lines
5.1 KiB
TypeScript
import { Constants } from 'librechat-data-provider';
|
|
import type { JsonSchemaType } from '@librechat/data-schemas';
|
|
import type { MCPConnection } from '~/mcp/connection';
|
|
import type * as t from '~/mcp/types';
|
|
import { isMCPDomainAllowed, extractMCPServerDomain } from '~/auth/domain';
|
|
import { MCPConnectionFactory } from '~/mcp/MCPConnectionFactory';
|
|
import { MCPDomainNotAllowedError } from '~/mcp/errors';
|
|
import { detectOAuthRequirement } from '~/mcp/oauth';
|
|
import { isEnabled } from '~/utils';
|
|
|
|
/**
|
|
* Inspects MCP servers to discover their metadata, capabilities, and tools.
|
|
* Connects to servers and populates configuration with OAuth requirements,
|
|
* server instructions, capabilities, and available tools.
|
|
*/
|
|
export class MCPServerInspector {
|
|
private constructor(
|
|
private readonly serverName: string,
|
|
private readonly config: t.ParsedServerConfig,
|
|
private connection: MCPConnection | undefined,
|
|
private readonly useSSRFProtection: boolean = false,
|
|
) {}
|
|
|
|
/**
|
|
* Inspects a server and returns an enriched configuration with metadata.
|
|
* Detects OAuth requirements and fetches server capabilities.
|
|
* @param serverName - The name of the server (used for tool function naming)
|
|
* @param rawConfig - The raw server configuration
|
|
* @param connection - The MCP connection
|
|
* @param allowedDomains - Optional list of allowed domains for remote transports
|
|
* @returns A fully processed and enriched configuration with server metadata
|
|
*/
|
|
public static async inspect(
|
|
serverName: string,
|
|
rawConfig: t.MCPOptions,
|
|
connection?: MCPConnection,
|
|
allowedDomains?: string[] | null,
|
|
): Promise<t.ParsedServerConfig> {
|
|
// Validate domain against allowlist BEFORE attempting connection
|
|
const isDomainAllowed = await isMCPDomainAllowed(rawConfig, allowedDomains);
|
|
if (!isDomainAllowed) {
|
|
const domain = extractMCPServerDomain(rawConfig);
|
|
throw new MCPDomainNotAllowedError(domain ?? 'unknown');
|
|
}
|
|
|
|
const useSSRFProtection = !Array.isArray(allowedDomains) || allowedDomains.length === 0;
|
|
const start = Date.now();
|
|
const inspector = new MCPServerInspector(serverName, rawConfig, connection, useSSRFProtection);
|
|
await inspector.inspectServer();
|
|
inspector.config.initDuration = Date.now() - start;
|
|
return inspector.config;
|
|
}
|
|
|
|
private async inspectServer(): Promise<void> {
|
|
await this.detectOAuth();
|
|
|
|
if (this.config.startup !== false && !this.config.requiresOAuth) {
|
|
let tempConnection = false;
|
|
if (!this.connection) {
|
|
tempConnection = true;
|
|
this.connection = await MCPConnectionFactory.create({
|
|
serverConfig: this.config,
|
|
serverName: this.serverName,
|
|
dbSourced: !!this.config.dbId,
|
|
useSSRFProtection: this.useSSRFProtection,
|
|
});
|
|
}
|
|
|
|
await Promise.allSettled([
|
|
this.fetchServerInstructions(),
|
|
this.fetchServerCapabilities(),
|
|
this.fetchToolFunctions(),
|
|
]);
|
|
|
|
if (tempConnection) await this.connection.disconnect();
|
|
}
|
|
}
|
|
|
|
private async detectOAuth(): Promise<void> {
|
|
if (this.config.requiresOAuth != null) return;
|
|
if (this.config.url == null || this.config.startup === false) {
|
|
this.config.requiresOAuth = false;
|
|
return;
|
|
}
|
|
|
|
// Admin-provided API key means no OAuth flow is needed
|
|
if (this.config.apiKey?.source === 'admin') {
|
|
this.config.requiresOAuth = false;
|
|
return;
|
|
}
|
|
|
|
const result = await detectOAuthRequirement(this.config.url);
|
|
this.config.requiresOAuth = result.requiresOAuth;
|
|
this.config.oauthMetadata = result.metadata;
|
|
}
|
|
|
|
private async fetchServerInstructions(): Promise<void> {
|
|
if (isEnabled(this.config.serverInstructions)) {
|
|
this.config.serverInstructions = this.connection!.client.getInstructions();
|
|
}
|
|
}
|
|
|
|
private async fetchServerCapabilities(): Promise<void> {
|
|
const capabilities = this.connection!.client.getServerCapabilities();
|
|
this.config.capabilities = JSON.stringify(capabilities);
|
|
const tools = await this.connection!.client.listTools();
|
|
this.config.tools = tools.tools.map((tool) => tool.name).join(', ');
|
|
}
|
|
|
|
private async fetchToolFunctions(): Promise<void> {
|
|
this.config.toolFunctions = await MCPServerInspector.getToolFunctions(
|
|
this.serverName,
|
|
this.connection!,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Converts server tools to LibreChat-compatible tool functions format.
|
|
* @param serverName - The name of the server
|
|
* @param connection - The MCP connection
|
|
* @returns Tool functions formatted for LibreChat
|
|
*/
|
|
public static async getToolFunctions(
|
|
serverName: string,
|
|
connection: MCPConnection,
|
|
): Promise<t.LCAvailableTools> {
|
|
const { tools }: t.MCPToolListResponse = await connection.client.listTools();
|
|
|
|
const toolFunctions: t.LCAvailableTools = {};
|
|
tools.forEach((tool) => {
|
|
const name = `${tool.name}${Constants.mcp_delimiter}${serverName}`;
|
|
toolFunctions[name] = {
|
|
type: 'function',
|
|
['function']: {
|
|
name,
|
|
description: tool.description,
|
|
parameters: tool.inputSchema as JsonSchemaType,
|
|
},
|
|
};
|
|
});
|
|
|
|
return toolFunctions;
|
|
}
|
|
}
|