LibreChat/packages/api/src/mcp/__tests__/ConnectionsRepository.test.ts

474 lines
16 KiB
TypeScript
Raw Normal View History

♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
import { logger } from '@librechat/data-schemas';
import { ConnectionsRepository } from '../ConnectionsRepository';
import { MCPConnectionFactory } from '../MCPConnectionFactory';
import { MCPConnection } from '../connection';
import type * as t from '../types';
// Mock external dependencies
jest.mock('@librechat/data-schemas', () => ({
logger: {
error: jest.fn(),
2025-11-26 15:11:36 +01:00
info: jest.fn(),
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
},
}));
jest.mock('../MCPConnectionFactory', () => ({
MCPConnectionFactory: {
create: jest.fn(),
},
}));
jest.mock('../connection');
2025-11-26 15:11:36 +01:00
// Mock the registry
const mockRegistryInstance = {
getServerConfig: jest.fn(),
getAllServerConfigs: jest.fn(),
};
2025-11-26 15:11:36 +01:00
jest.mock('../registry/MCPServersRegistry', () => ({
MCPServersRegistry: {
getInstance: () => mockRegistryInstance,
2025-11-26 15:11:36 +01:00
},
}));
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
const mockLogger = logger as jest.Mocked<typeof logger>;
// Use mocked registry instance
const mockRegistry = mockRegistryInstance as jest.Mocked<typeof mockRegistryInstance>;
2025-11-26 15:11:36 +01:00
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
describe('ConnectionsRepository', () => {
let repository: ConnectionsRepository;
2025-11-26 15:11:36 +01:00
let mockServerConfigs: Record<string, t.ParsedServerConfig>;
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
let mockConnection: jest.Mocked<MCPConnection>;
beforeEach(() => {
mockServerConfigs = {
server1: { url: 'http://localhost:3001' },
server2: { command: 'test-command', args: ['--test'] },
server3: { url: 'ws://localhost:8080', type: 'websocket' },
};
2025-11-26 15:11:36 +01:00
// Setup mock registry
// eslint-disable-next-line @typescript-eslint/no-unused-vars
mockRegistry.getServerConfig = jest.fn((serverName: string, ownerId?: string) =>
Promise.resolve(mockServerConfigs[serverName] || undefined),
) as jest.Mock;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
mockRegistry.getAllServerConfigs = jest.fn((ownerId?: string) =>
Promise.resolve(mockServerConfigs),
) as jest.Mock;
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
mockConnection = {
isConnected: jest.fn().mockResolvedValue(true),
disconnect: jest.fn().mockResolvedValue(undefined),
2025-11-26 15:11:36 +01:00
createdAt: Date.now(),
isStale: jest.fn().mockReturnValue(false),
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
} as unknown as jest.Mocked<MCPConnection>;
(MCPConnectionFactory.create as jest.Mock).mockResolvedValue(mockConnection);
2025-11-26 15:11:36 +01:00
// Create repository with undefined ownerId (app-level)
repository = new ConnectionsRepository(undefined);
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('has', () => {
2025-11-26 15:11:36 +01:00
it('should return true for existing server', async () => {
expect(await repository.has('server1')).toBe(true);
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
});
2025-11-26 15:11:36 +01:00
it('should return false for non-existing server', async () => {
expect(await repository.has('nonexistent')).toBe(false);
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
});
});
describe('get', () => {
it('should return existing connected connection', async () => {
mockConnection.isConnected.mockResolvedValue(true);
repository['connections'].set('server1', mockConnection);
const result = await repository.get('server1');
expect(result).toBe(mockConnection);
expect(MCPConnectionFactory.create).not.toHaveBeenCalled();
});
it('should create new connection if none exists', async () => {
const result = await repository.get('server1');
expect(result).toBe(mockConnection);
expect(MCPConnectionFactory.create).toHaveBeenCalledWith(
{
serverName: 'server1',
serverConfig: mockServerConfigs.server1,
},
undefined,
);
expect(repository['connections'].get('server1')).toBe(mockConnection);
});
it('should create new connection if existing connection is not connected', async () => {
const oldConnection = {
isConnected: jest.fn().mockResolvedValue(false),
disconnect: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<MCPConnection>;
repository['connections'].set('server1', oldConnection);
const result = await repository.get('server1');
expect(result).toBe(mockConnection);
expect(oldConnection.disconnect).toHaveBeenCalled();
expect(MCPConnectionFactory.create).toHaveBeenCalledWith(
{
serverName: 'server1',
serverConfig: mockServerConfigs.server1,
},
undefined,
);
});
2025-11-26 15:11:36 +01:00
it('should recreate connection when existing connection is stale', async () => {
const connectionCreatedAt = Date.now();
const configCachedAt = connectionCreatedAt + 10000; // Config updated 10 seconds after connection was created
const staleConnection = {
isConnected: jest.fn().mockResolvedValue(true),
disconnect: jest.fn().mockResolvedValue(undefined),
createdAt: connectionCreatedAt,
isStale: jest.fn().mockReturnValue(true),
} as unknown as jest.Mocked<MCPConnection>;
🏗️ feat: Dynamic MCP Server Infrastructure with Access Control (#10787) * Feature: Dynamic MCP Server with Full UI Management * 🚦 feat: Add MCP Connection Status icons to MCPBuilder panel (#10805) * feature: Add MCP server connection status icons to MCPBuilder panel * refactor: Simplify MCPConfigDialog rendering in MCPBuilderPanel --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai> * fix: address code review feedback for MCP server management - Fix OAuth secret preservation to avoid mutating input parameter by creating a merged config copy in ServerConfigsDB.update() - Improve error handling in getResourcePermissionsMap to propagate critical errors instead of silently returning empty Map - Extract duplicated MCP server filter logic by exposing selectableServers from useMCPServerManager hook and using it in MCPSelect component * test: Update PermissionService tests to throw errors on invalid resource types - Changed the test for handling invalid resource types to ensure it throws an error instead of returning an empty permissions map. - Updated the expectation to check for the specific error message when an invalid resource type is provided. * feat: Implement retry logic for MCP server creation to handle race conditions - Enhanced the createMCPServer method to include retry logic with exponential backoff for handling duplicate key errors during concurrent server creation. - Updated tests to verify that all concurrent requests succeed and that unique server names are generated. - Added a helper function to identify MongoDB duplicate key errors, improving error handling during server creation. * refactor: StatusIcon to use CircleCheck for connected status - Replaced the PlugZap icon with CircleCheck in the ConnectedStatusIcon component to better represent the connected state. - Ensured consistent icon usage across the component for improved visual clarity. * test: Update AccessControlService tests to throw errors on invalid resource types - Modified the test for invalid resource types to ensure it throws an error with a specific message instead of returning an empty permissions map. - This change enhances error handling and improves test coverage for the AccessControlService. * fix: Update error message for missing server name in MCP server retrieval - Changed the error message returned when the server name is not provided from 'MCP ID is required' to 'Server name is required' for better clarity and accuracy in the API response. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-04 21:37:23 +01:00
// Update server config with updatedAt timestamp
2025-11-26 15:11:36 +01:00
const configWithCachedAt = {
...mockServerConfigs.server1,
🏗️ feat: Dynamic MCP Server Infrastructure with Access Control (#10787) * Feature: Dynamic MCP Server with Full UI Management * 🚦 feat: Add MCP Connection Status icons to MCPBuilder panel (#10805) * feature: Add MCP server connection status icons to MCPBuilder panel * refactor: Simplify MCPConfigDialog rendering in MCPBuilderPanel --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai> * fix: address code review feedback for MCP server management - Fix OAuth secret preservation to avoid mutating input parameter by creating a merged config copy in ServerConfigsDB.update() - Improve error handling in getResourcePermissionsMap to propagate critical errors instead of silently returning empty Map - Extract duplicated MCP server filter logic by exposing selectableServers from useMCPServerManager hook and using it in MCPSelect component * test: Update PermissionService tests to throw errors on invalid resource types - Changed the test for handling invalid resource types to ensure it throws an error instead of returning an empty permissions map. - Updated the expectation to check for the specific error message when an invalid resource type is provided. * feat: Implement retry logic for MCP server creation to handle race conditions - Enhanced the createMCPServer method to include retry logic with exponential backoff for handling duplicate key errors during concurrent server creation. - Updated tests to verify that all concurrent requests succeed and that unique server names are generated. - Added a helper function to identify MongoDB duplicate key errors, improving error handling during server creation. * refactor: StatusIcon to use CircleCheck for connected status - Replaced the PlugZap icon with CircleCheck in the ConnectedStatusIcon component to better represent the connected state. - Ensured consistent icon usage across the component for improved visual clarity. * test: Update AccessControlService tests to throw errors on invalid resource types - Modified the test for invalid resource types to ensure it throws an error with a specific message instead of returning an empty permissions map. - This change enhances error handling and improves test coverage for the AccessControlService. * fix: Update error message for missing server name in MCP server retrieval - Changed the error message returned when the server name is not provided from 'MCP ID is required' to 'Server name is required' for better clarity and accuracy in the API response. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-04 21:37:23 +01:00
updatedAt: configCachedAt,
2025-11-26 15:11:36 +01:00
};
mockRegistry.getServerConfig.mockResolvedValueOnce(configWithCachedAt);
repository['connections'].set('server1', staleConnection);
const result = await repository.get('server1');
🏗️ feat: Dynamic MCP Server Infrastructure with Access Control (#10787) * Feature: Dynamic MCP Server with Full UI Management * 🚦 feat: Add MCP Connection Status icons to MCPBuilder panel (#10805) * feature: Add MCP server connection status icons to MCPBuilder panel * refactor: Simplify MCPConfigDialog rendering in MCPBuilderPanel --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai> * fix: address code review feedback for MCP server management - Fix OAuth secret preservation to avoid mutating input parameter by creating a merged config copy in ServerConfigsDB.update() - Improve error handling in getResourcePermissionsMap to propagate critical errors instead of silently returning empty Map - Extract duplicated MCP server filter logic by exposing selectableServers from useMCPServerManager hook and using it in MCPSelect component * test: Update PermissionService tests to throw errors on invalid resource types - Changed the test for handling invalid resource types to ensure it throws an error instead of returning an empty permissions map. - Updated the expectation to check for the specific error message when an invalid resource type is provided. * feat: Implement retry logic for MCP server creation to handle race conditions - Enhanced the createMCPServer method to include retry logic with exponential backoff for handling duplicate key errors during concurrent server creation. - Updated tests to verify that all concurrent requests succeed and that unique server names are generated. - Added a helper function to identify MongoDB duplicate key errors, improving error handling during server creation. * refactor: StatusIcon to use CircleCheck for connected status - Replaced the PlugZap icon with CircleCheck in the ConnectedStatusIcon component to better represent the connected state. - Ensured consistent icon usage across the component for improved visual clarity. * test: Update AccessControlService tests to throw errors on invalid resource types - Modified the test for invalid resource types to ensure it throws an error with a specific message instead of returning an empty permissions map. - This change enhances error handling and improves test coverage for the AccessControlService. * fix: Update error message for missing server name in MCP server retrieval - Changed the error message returned when the server name is not provided from 'MCP ID is required' to 'Server name is required' for better clarity and accuracy in the API response. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-04 21:37:23 +01:00
// Verify stale check was called with the config's updatedAt timestamp
2025-11-26 15:11:36 +01:00
expect(staleConnection.isStale).toHaveBeenCalledWith(configCachedAt);
// Verify old connection was disconnected
expect(staleConnection.disconnect).toHaveBeenCalled();
// Verify new connection was created
expect(MCPConnectionFactory.create).toHaveBeenCalledWith(
{
serverName: 'server1',
serverConfig: configWithCachedAt,
},
undefined,
);
// Verify new connection is returned
expect(result).toBe(mockConnection);
// Verify the new connection replaced the stale one in the repository
expect(repository['connections'].get('server1')).toBe(mockConnection);
expect(repository['connections'].get('server1')).not.toBe(staleConnection);
});
it('should return existing connection when it is not stale', async () => {
const connectionCreatedAt = Date.now();
const configCachedAt = connectionCreatedAt - 10000; // Config is older than connection
const freshConnection = {
isConnected: jest.fn().mockResolvedValue(true),
disconnect: jest.fn().mockResolvedValue(undefined),
createdAt: connectionCreatedAt,
isStale: jest.fn().mockReturnValue(false),
} as unknown as jest.Mocked<MCPConnection>;
🏗️ feat: Dynamic MCP Server Infrastructure with Access Control (#10787) * Feature: Dynamic MCP Server with Full UI Management * 🚦 feat: Add MCP Connection Status icons to MCPBuilder panel (#10805) * feature: Add MCP server connection status icons to MCPBuilder panel * refactor: Simplify MCPConfigDialog rendering in MCPBuilderPanel --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai> * fix: address code review feedback for MCP server management - Fix OAuth secret preservation to avoid mutating input parameter by creating a merged config copy in ServerConfigsDB.update() - Improve error handling in getResourcePermissionsMap to propagate critical errors instead of silently returning empty Map - Extract duplicated MCP server filter logic by exposing selectableServers from useMCPServerManager hook and using it in MCPSelect component * test: Update PermissionService tests to throw errors on invalid resource types - Changed the test for handling invalid resource types to ensure it throws an error instead of returning an empty permissions map. - Updated the expectation to check for the specific error message when an invalid resource type is provided. * feat: Implement retry logic for MCP server creation to handle race conditions - Enhanced the createMCPServer method to include retry logic with exponential backoff for handling duplicate key errors during concurrent server creation. - Updated tests to verify that all concurrent requests succeed and that unique server names are generated. - Added a helper function to identify MongoDB duplicate key errors, improving error handling during server creation. * refactor: StatusIcon to use CircleCheck for connected status - Replaced the PlugZap icon with CircleCheck in the ConnectedStatusIcon component to better represent the connected state. - Ensured consistent icon usage across the component for improved visual clarity. * test: Update AccessControlService tests to throw errors on invalid resource types - Modified the test for invalid resource types to ensure it throws an error with a specific message instead of returning an empty permissions map. - This change enhances error handling and improves test coverage for the AccessControlService. * fix: Update error message for missing server name in MCP server retrieval - Changed the error message returned when the server name is not provided from 'MCP ID is required' to 'Server name is required' for better clarity and accuracy in the API response. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-04 21:37:23 +01:00
// Update server config with updatedAt timestamp
2025-11-26 15:11:36 +01:00
const configWithCachedAt = {
...mockServerConfigs.server1,
🏗️ feat: Dynamic MCP Server Infrastructure with Access Control (#10787) * Feature: Dynamic MCP Server with Full UI Management * 🚦 feat: Add MCP Connection Status icons to MCPBuilder panel (#10805) * feature: Add MCP server connection status icons to MCPBuilder panel * refactor: Simplify MCPConfigDialog rendering in MCPBuilderPanel --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai> * fix: address code review feedback for MCP server management - Fix OAuth secret preservation to avoid mutating input parameter by creating a merged config copy in ServerConfigsDB.update() - Improve error handling in getResourcePermissionsMap to propagate critical errors instead of silently returning empty Map - Extract duplicated MCP server filter logic by exposing selectableServers from useMCPServerManager hook and using it in MCPSelect component * test: Update PermissionService tests to throw errors on invalid resource types - Changed the test for handling invalid resource types to ensure it throws an error instead of returning an empty permissions map. - Updated the expectation to check for the specific error message when an invalid resource type is provided. * feat: Implement retry logic for MCP server creation to handle race conditions - Enhanced the createMCPServer method to include retry logic with exponential backoff for handling duplicate key errors during concurrent server creation. - Updated tests to verify that all concurrent requests succeed and that unique server names are generated. - Added a helper function to identify MongoDB duplicate key errors, improving error handling during server creation. * refactor: StatusIcon to use CircleCheck for connected status - Replaced the PlugZap icon with CircleCheck in the ConnectedStatusIcon component to better represent the connected state. - Ensured consistent icon usage across the component for improved visual clarity. * test: Update AccessControlService tests to throw errors on invalid resource types - Modified the test for invalid resource types to ensure it throws an error with a specific message instead of returning an empty permissions map. - This change enhances error handling and improves test coverage for the AccessControlService. * fix: Update error message for missing server name in MCP server retrieval - Changed the error message returned when the server name is not provided from 'MCP ID is required' to 'Server name is required' for better clarity and accuracy in the API response. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-04 21:37:23 +01:00
updatedAt: configCachedAt,
2025-11-26 15:11:36 +01:00
};
mockRegistry.getServerConfig.mockResolvedValueOnce(configWithCachedAt);
repository['connections'].set('server1', freshConnection);
const result = await repository.get('server1');
// Verify stale check was called
expect(freshConnection.isStale).toHaveBeenCalledWith(configCachedAt);
// Verify connection was not disconnected
expect(freshConnection.disconnect).not.toHaveBeenCalled();
// Verify no new connection was created
expect(MCPConnectionFactory.create).not.toHaveBeenCalled();
// Verify existing connection is returned
expect(result).toBe(freshConnection);
// Verify repository still has the same connection
expect(repository['connections'].get('server1')).toBe(freshConnection);
});
//todo revist later when async getAll(): in packages/api/src/mcp/ConnectionsRepository.ts is refactored
it.skip('should throw error for non-existent server configuration', async () => {
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
await expect(repository.get('nonexistent')).rejects.toThrow(
'[MCP][nonexistent] Server not found in configuration',
);
});
it('should handle MCPConnectionFactory.create errors', async () => {
const createError = new Error('Connection creation failed');
(MCPConnectionFactory.create as jest.Mock).mockRejectedValue(createError);
await expect(repository.get('server1')).rejects.toThrow('Connection creation failed');
});
});
describe('getMany', () => {
it('should return connections for multiple servers', async () => {
const result = await repository.getMany(['server1', 'server3']);
expect(result).toBeInstanceOf(Map);
expect(result.size).toBe(2);
expect(result.get('server1')).toBe(mockConnection);
expect(result.get('server3')).toBe(mockConnection);
});
});
describe('getLoaded', () => {
it('should return connections for loaded servers only', async () => {
// Load one connection
await repository.get('server1');
const result = await repository.getLoaded();
expect(result).toBeInstanceOf(Map);
expect(result.size).toBe(1);
expect(result.get('server1')).toBe(mockConnection);
});
it('should return empty map when no connections are loaded', async () => {
const result = await repository.getLoaded();
expect(result).toBeInstanceOf(Map);
expect(result.size).toBe(0);
});
});
describe('getAll', () => {
it('should return connections for all configured servers', async () => {
const result = await repository.getAll();
expect(result).toBeInstanceOf(Map);
expect(result.size).toBe(3);
expect(result.get('server1')).toBe(mockConnection);
expect(result.get('server2')).toBe(mockConnection);
expect(result.get('server3')).toBe(mockConnection);
});
});
describe('disconnect', () => {
it('should disconnect and remove existing connection', async () => {
repository['connections'].set('server1', mockConnection);
await repository.disconnect('server1');
expect(mockConnection.disconnect).toHaveBeenCalled();
expect(repository['connections'].has('server1')).toBe(false);
});
it('should handle disconnect error gracefully', async () => {
const disconnectError = new Error('Disconnect failed');
mockConnection.disconnect.mockRejectedValue(disconnectError);
repository['connections'].set('server1', mockConnection);
await repository.disconnect('server1');
expect(mockConnection.disconnect).toHaveBeenCalled();
expect(repository['connections'].has('server1')).toBe(false);
expect(mockLogger.error).toHaveBeenCalledWith(
'[MCP][server1] Error disconnecting',
disconnectError,
);
});
});
describe('disconnectAll', () => {
it('should disconnect all active connections', () => {
const mockConnection1 = {
disconnect: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<MCPConnection>;
const mockConnection2 = {
disconnect: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<MCPConnection>;
const mockConnection3 = {
disconnect: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<MCPConnection>;
repository['connections'].set('server1', mockConnection1);
repository['connections'].set('server2', mockConnection2);
repository['connections'].set('server3', mockConnection3);
const promises = repository.disconnectAll();
expect(promises).toHaveLength(3);
expect(Array.isArray(promises)).toBe(true);
});
});
🗃️ refactor: Simplify MCP Server Config to Two-Repository Pattern (#10705) * refactor(mcp): simplify registry to two-repository architecture with explicit storage * Chore: address AI Review comments * Simplify MCP config cache architecture and remove legacy code: Follow-up cleanup to commit d2bfdd033 which refactored MCP registry to two-repository architecture. This removes leftover legacy abstractions that were no longer used. What changed: - Simplified ServerConfigsCacheFactory.create() from 3 params to 2 (namespace, leaderOnly) - Removed unused scope: 'Shared' | 'Private' parameter (only 'Shared' was ever used) - Removed dead set() and getNamespace() methods from cache classes - Updated JSDoc to reflect two-repository architecture (Cache + DB) instead of old three-tier system - Fixed stale mocks and comments referencing removed sharedAppServers, sharedUserServers, privateServersCache Files changed: - ServerConfigsCacheFactory.ts - Simplified factory signature - ServerConfigsCacheRedis.ts - Removed scope, renamed owner→namespace - ServerConfigsCacheInMemory.ts - Removed unused methods - MCPServersRegistry.ts - Updated JSDoc, simplified factory call - RegistryStatusCache.ts - Removed stale JSDoc reference - MCPManager.test.ts - Fixed legacy mock - ServerConfigsCacheFactory.test.ts - Updated test assertions * fix: Update error message in MCPServersRegistry for clarity --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
2025-11-28 16:07:09 +01:00
describe('Connection policy (isAllowedToConnectToServer)', () => {
describe('App-level repository (ownerId = undefined)', () => {
beforeEach(() => {
repository = new ConnectionsRepository(undefined);
});
it('should allow connection to regular servers (startup !== false, !requiresOAuth)', async () => {
mockServerConfigs.regularServer = {
type: 'stdio',
command: 'test',
args: [],
startup: true,
requiresOAuth: false,
};
expect(await repository.has('regularServer')).toBe(true);
});
it('should allow connection when startup is undefined and requiresOAuth is false', async () => {
mockServerConfigs.defaultServer = {
type: 'stdio',
command: 'test',
args: [],
requiresOAuth: false,
};
expect(await repository.has('defaultServer')).toBe(true);
});
it('should NOT allow connection to OAuth servers', async () => {
mockServerConfigs.oauthServer = {
type: 'streamable-http',
url: 'http://example.com',
requiresOAuth: true,
};
expect(await repository.has('oauthServer')).toBe(false);
});
it('should NOT allow connection to disabled servers (startup: false)', async () => {
mockServerConfigs.disabledServer = {
type: 'stdio',
command: 'test',
args: [],
startup: false,
requiresOAuth: false,
};
expect(await repository.has('disabledServer')).toBe(false);
});
it('should NOT allow connection to OAuth servers that are also disabled', async () => {
mockServerConfigs.oauthDisabledServer = {
type: 'streamable-http',
url: 'http://example.com',
startup: false,
requiresOAuth: true,
};
expect(await repository.has('oauthDisabledServer')).toBe(false);
});
it('should disconnect existing connection when server becomes not allowed', async () => {
// Initially setup as regular server
mockServerConfigs.changingServer = {
type: 'stdio',
command: 'test',
args: [],
requiresOAuth: false,
};
// Create connection
const connection = await repository.get('changingServer');
expect(connection).toBe(mockConnection);
expect(repository['connections'].has('changingServer')).toBe(true);
// Change config to require OAuth (app-level can't connect)
mockServerConfigs.changingServer = {
type: 'streamable-http',
url: 'http://example.com',
requiresOAuth: true,
};
// Check if connection is allowed
const allowed = await repository.has('changingServer');
expect(allowed).toBe(false);
expect(mockConnection.disconnect).toHaveBeenCalled();
});
});
describe('User-level repository (ownerId = userId)', () => {
beforeEach(() => {
repository = new ConnectionsRepository('user123');
});
it('should allow connection to regular servers', async () => {
mockServerConfigs.regularServer = {
type: 'stdio',
command: 'test',
args: [],
startup: true,
requiresOAuth: false,
};
expect(await repository.has('regularServer')).toBe(true);
});
it('should allow connection to OAuth servers', async () => {
mockServerConfigs.oauthServer = {
type: 'streamable-http',
url: 'http://example.com',
requiresOAuth: true,
};
expect(await repository.has('oauthServer')).toBe(true);
});
it('should allow connection to disabled servers (startup: false)', async () => {
mockServerConfigs.disabledServer = {
type: 'stdio',
command: 'test',
args: [],
startup: false,
requiresOAuth: false,
};
expect(await repository.has('disabledServer')).toBe(true);
});
it('should allow connection to OAuth servers that are also disabled', async () => {
mockServerConfigs.oauthDisabledServer = {
type: 'streamable-http',
url: 'http://example.com',
startup: false,
requiresOAuth: true,
};
expect(await repository.has('oauthDisabledServer')).toBe(true);
});
it('should return null from get() when server config does not exist', async () => {
const connection = await repository.get('nonexistent');
expect(connection).toBeNull();
});
});
});
♻️ refactor: MCPManager for Scalability, Fix App-Level Detection, Add Lazy Connections (#8930) * feat: MCP Connection management overhaul - Making MCPManager manageable Refactor the monolithic MCPManager into focused, single-responsibility classes: • MCPServersRegistry: Server configuration discovery and metadata management • UserConnectionManager: Manages user-level connections • ConnectionsRepository: Low-level connection pool with lazy loading • MCPConnectionFactory: Handles MCP connection creation with OAuth support New Features: • Lazy loading of app-level connections for horizontal scaling • Automatic reconnection for app-level connections • Enhanced OAuth detection with explicit requiresOAuth flag • Centralized MCP configuration management Bug Fixes: • App-level connection detection in MCPManager.callTool • MCP Connection Reinitialization route behavior Optimizations: • MCPConnection.isConnected() caching to reduce overhead • Concurrent server metadata retrieval instead of sequential This refactoring addresses scalability bottlenecks and improves reliability while maintaining backward compatibility with existing configurations. * feat: Enabled import order in eslint. * # Moved tests to __tests__ folder # added tests for MCPServersRegistry.ts * # Add unit tests for ConnectionsRepository functionality * # Add unit tests for MCPConnectionFactory functionality * # Reorganize MCP connection tests and improve error handling * # reordering imports * # Update testPathIgnorePatterns in jest.config.mjs to exclude development TypeScript files * # removed mcp/manager.ts
2025-08-13 09:45:06 -06:00
});