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>;
// Update server config with lastUpdatedAt timestamp
const configWithCachedAt = {
...mockServerConfigs.server1,
lastUpdatedAt: configCachedAt,
};
mockRegistry.getServerConfig.mockResolvedValueOnce(configWithCachedAt);
repository['connections'].set('server1', staleConnection);
const result = await repository.get('server1');
// Verify stale check was called with the config's lastUpdatedAt timestamp
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>;
// Update server config with lastUpdatedAt timestamp
const configWithCachedAt = {
...mockServerConfigs.server1,
lastUpdatedAt: configCachedAt,
};
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
});