LibreChat/packages/api/src/mcp/registry/MCPServerInspector.ts

141 lines
4.8 KiB
TypeScript
Raw Normal View History

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';
🔒 feat: Add MCP server domain restrictions for remote transports (#11013) * 🔒 feat: Add MCP server domain restrictions for remote transports * 🔒 feat: Implement comprehensive MCP error handling and domain validation - Added `handleMCPError` function to centralize error responses for domain restrictions and inspection failures. - Introduced custom error classes: `MCPDomainNotAllowedError` and `MCPInspectionFailedError` for better error management. - Updated MCP server controllers to utilize the new error handling mechanism. - Enhanced domain validation logic in `createMCPTools` and `createMCPTool` functions to prevent operations on disallowed domains. - Added tests for runtime domain validation scenarios to ensure correct behavior. * chore: import order * 🔒 feat: Enhance domain validation in MCP tools with user role-based restrictions - Integrated `getAppConfig` to fetch allowed domains based on user roles in `createMCPTools` and `createMCPTool` functions. - Removed the deprecated `getAllowedDomains` method from `MCPServersRegistry`. - Updated tests to verify domain restrictions are applied correctly based on user roles. - Ensured that domain validation logic is consistent and efficient across tool creation processes. * 🔒 test: Refactor MCP tests to utilize configurable app settings - Introduced a mock for `getAppConfig` to enhance test flexibility. - Removed redundant mock definition to streamline test setup. - Ensured tests are aligned with the latest domain validation logic. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-18 19:57:49 +01:00
import { isMCPDomainAllowed, extractMCPServerDomain } from '~/auth/domain';
import { MCPConnectionFactory } from '~/mcp/MCPConnectionFactory';
🔒 feat: Add MCP server domain restrictions for remote transports (#11013) * 🔒 feat: Add MCP server domain restrictions for remote transports * 🔒 feat: Implement comprehensive MCP error handling and domain validation - Added `handleMCPError` function to centralize error responses for domain restrictions and inspection failures. - Introduced custom error classes: `MCPDomainNotAllowedError` and `MCPInspectionFailedError` for better error management. - Updated MCP server controllers to utilize the new error handling mechanism. - Enhanced domain validation logic in `createMCPTools` and `createMCPTool` functions to prevent operations on disallowed domains. - Added tests for runtime domain validation scenarios to ensure correct behavior. * chore: import order * 🔒 feat: Enhance domain validation in MCP tools with user role-based restrictions - Integrated `getAppConfig` to fetch allowed domains based on user roles in `createMCPTools` and `createMCPTool` functions. - Removed the deprecated `getAllowedDomains` method from `MCPServersRegistry`. - Updated tests to verify domain restrictions are applied correctly based on user roles. - Ensured that domain validation logic is consistent and efficient across tool creation processes. * 🔒 test: Refactor MCP tests to utilize configurable app settings - Introduced a mock for `getAppConfig` to enhance test flexibility. - Removed redundant mock definition to streamline test setup. - Ensured tests are aligned with the latest domain validation logic. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-18 19:57:49 +01:00
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,
) {}
/**
* 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
🔒 feat: Add MCP server domain restrictions for remote transports (#11013) * 🔒 feat: Add MCP server domain restrictions for remote transports * 🔒 feat: Implement comprehensive MCP error handling and domain validation - Added `handleMCPError` function to centralize error responses for domain restrictions and inspection failures. - Introduced custom error classes: `MCPDomainNotAllowedError` and `MCPInspectionFailedError` for better error management. - Updated MCP server controllers to utilize the new error handling mechanism. - Enhanced domain validation logic in `createMCPTools` and `createMCPTool` functions to prevent operations on disallowed domains. - Added tests for runtime domain validation scenarios to ensure correct behavior. * chore: import order * 🔒 feat: Enhance domain validation in MCP tools with user role-based restrictions - Integrated `getAppConfig` to fetch allowed domains based on user roles in `createMCPTools` and `createMCPTool` functions. - Removed the deprecated `getAllowedDomains` method from `MCPServersRegistry`. - Updated tests to verify domain restrictions are applied correctly based on user roles. - Ensured that domain validation logic is consistent and efficient across tool creation processes. * 🔒 test: Refactor MCP tests to utilize configurable app settings - Introduced a mock for `getAppConfig` to enhance test flexibility. - Removed redundant mock definition to streamline test setup. - Ensured tests are aligned with the latest domain validation logic. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-18 19:57:49 +01:00
* @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,
🔒 feat: Add MCP server domain restrictions for remote transports (#11013) * 🔒 feat: Add MCP server domain restrictions for remote transports * 🔒 feat: Implement comprehensive MCP error handling and domain validation - Added `handleMCPError` function to centralize error responses for domain restrictions and inspection failures. - Introduced custom error classes: `MCPDomainNotAllowedError` and `MCPInspectionFailedError` for better error management. - Updated MCP server controllers to utilize the new error handling mechanism. - Enhanced domain validation logic in `createMCPTools` and `createMCPTool` functions to prevent operations on disallowed domains. - Added tests for runtime domain validation scenarios to ensure correct behavior. * chore: import order * 🔒 feat: Enhance domain validation in MCP tools with user role-based restrictions - Integrated `getAppConfig` to fetch allowed domains based on user roles in `createMCPTools` and `createMCPTool` functions. - Removed the deprecated `getAllowedDomains` method from `MCPServersRegistry`. - Updated tests to verify domain restrictions are applied correctly based on user roles. - Ensured that domain validation logic is consistent and efficient across tool creation processes. * 🔒 test: Refactor MCP tests to utilize configurable app settings - Introduced a mock for `getAppConfig` to enhance test flexibility. - Removed redundant mock definition to streamline test setup. - Ensured tests are aligned with the latest domain validation logic. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-18 19:57:49 +01:00
allowedDomains?: string[] | null,
): Promise<t.ParsedServerConfig> {
🔒 feat: Add MCP server domain restrictions for remote transports (#11013) * 🔒 feat: Add MCP server domain restrictions for remote transports * 🔒 feat: Implement comprehensive MCP error handling and domain validation - Added `handleMCPError` function to centralize error responses for domain restrictions and inspection failures. - Introduced custom error classes: `MCPDomainNotAllowedError` and `MCPInspectionFailedError` for better error management. - Updated MCP server controllers to utilize the new error handling mechanism. - Enhanced domain validation logic in `createMCPTools` and `createMCPTool` functions to prevent operations on disallowed domains. - Added tests for runtime domain validation scenarios to ensure correct behavior. * chore: import order * 🔒 feat: Enhance domain validation in MCP tools with user role-based restrictions - Integrated `getAppConfig` to fetch allowed domains based on user roles in `createMCPTools` and `createMCPTool` functions. - Removed the deprecated `getAllowedDomains` method from `MCPServersRegistry`. - Updated tests to verify domain restrictions are applied correctly based on user roles. - Ensured that domain validation logic is consistent and efficient across tool creation processes. * 🔒 test: Refactor MCP tests to utilize configurable app settings - Introduced a mock for `getAppConfig` to enhance test flexibility. - Removed redundant mock definition to streamline test setup. - Ensured tests are aligned with the latest domain validation logic. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-18 19:57:49 +01:00
// 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 start = Date.now();
const inspector = new MCPServerInspector(serverName, rawConfig, connection);
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({
serverName: this.serverName,
serverConfig: this.config,
});
}
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;
}
}