import { logger } from '@librechat/data-schemas'; import type * as t from '~/mcp/types'; import { MCPManager } from '~/mcp/MCPManager'; import { mcpServersRegistry } from '~/mcp/registry/MCPServersRegistry'; import { MCPServersInitializer } from '~/mcp/registry/MCPServersInitializer'; import { MCPServerInspector } from '~/mcp/registry/MCPServerInspector'; import { ConnectionsRepository } from '~/mcp/ConnectionsRepository'; import { MCPConnection } from '../connection'; // Mock external dependencies jest.mock('@librechat/data-schemas', () => ({ logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn(), }, })); jest.mock('~/mcp/registry/MCPServersRegistry', () => ({ mcpServersRegistry: { sharedAppServers: { getAll: jest.fn(), }, getServerConfig: jest.fn(), getAllServerConfigs: jest.fn(), getOAuthServers: jest.fn(), }, })); jest.mock('~/mcp/registry/MCPServersInitializer', () => ({ MCPServersInitializer: { initialize: jest.fn(), }, })); jest.mock('~/mcp/registry/MCPServerInspector'); jest.mock('~/mcp/ConnectionsRepository'); const mockLogger = logger as jest.Mocked; describe('MCPManager', () => { const userId = 'test-user-123'; const serverName = 'test_server'; beforeEach(() => { // Reset MCPManager singleton state (MCPManager as unknown as { instance: null }).instance = null; jest.clearAllMocks(); // Set up default mock implementations (MCPServersInitializer.initialize as jest.Mock).mockResolvedValue(undefined); (mcpServersRegistry.sharedAppServers.getAll as jest.Mock).mockResolvedValue({}); (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({}); }); function mockAppConnections( appConnectionsConfig: Partial, ): jest.MockedClass { const mock = { has: jest.fn().mockResolvedValue(false), get: jest.fn().mockResolvedValue({} as unknown as MCPConnection), ...appConnectionsConfig, }; return ( ConnectionsRepository as jest.MockedClass ).mockImplementation(() => mock as unknown as ConnectionsRepository); } function newMCPServersConfig(serverNameOverride?: string): t.MCPServers { return { [serverNameOverride ?? serverName]: { type: 'stdio', command: 'test', args: [], }, }; } describe('getAppToolFunctions', () => { it('should return empty object when no servers have tool functions', async () => { (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ server1: { type: 'stdio', command: 'test', args: [] }, server2: { type: 'stdio', command: 'test2', args: [] }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.getAppToolFunctions(); expect(result).toEqual({}); }); it('should collect tool functions from multiple servers', async () => { const toolFunctions1 = { tool1_mcp_server1: { type: 'function' as const, function: { name: 'tool1_mcp_server1', description: 'Tool 1', parameters: { type: 'object' as const }, }, }, }; const toolFunctions2 = { tool2_mcp_server2: { type: 'function' as const, function: { name: 'tool2_mcp_server2', description: 'Tool 2', parameters: { type: 'object' as const }, }, }, }; (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ server1: { type: 'stdio', command: 'test', args: [], toolFunctions: toolFunctions1, }, server2: { type: 'stdio', command: 'test2', args: [], toolFunctions: toolFunctions2, }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.getAppToolFunctions(); expect(result).toEqual({ ...toolFunctions1, ...toolFunctions2, }); }); it('should handle servers with null or undefined toolFunctions', async () => { const toolFunctions1 = { tool1_mcp_server1: { type: 'function' as const, function: { name: 'tool1_mcp_server1', description: 'Tool 1', parameters: { type: 'object' as const }, }, }, }; (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ server1: { type: 'stdio', command: 'test', args: [], toolFunctions: toolFunctions1, }, server2: { type: 'stdio', command: 'test2', args: [], toolFunctions: null, }, server3: { type: 'stdio', command: 'test3', args: [], }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.getAppToolFunctions(); expect(result).toEqual(toolFunctions1); }); }); describe('formatInstructionsForContext', () => { it('should return empty string when no servers have instructions', async () => { (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ server1: { type: 'stdio', command: 'test', args: [] }, server2: { type: 'stdio', command: 'test2', args: [] }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.formatInstructionsForContext(); expect(result).toBe(''); }); it('should format instructions from multiple servers', async () => { (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ github: { type: 'sse', url: 'https://api.github.com', serverInstructions: 'Use GitHub API with care', }, files: { type: 'stdio', command: 'node', args: ['files.js'], serverInstructions: 'Only read/write files in allowed directories', }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.formatInstructionsForContext(); expect(result).toContain('# MCP Server Instructions'); expect(result).toContain('## github MCP Server Instructions'); expect(result).toContain('Use GitHub API with care'); expect(result).toContain('## files MCP Server Instructions'); expect(result).toContain('Only read/write files in allowed directories'); }); it('should filter instructions by server names when provided', async () => { (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ github: { type: 'sse', url: 'https://api.github.com', serverInstructions: 'Use GitHub API with care', }, files: { type: 'stdio', command: 'node', args: ['files.js'], serverInstructions: 'Only read/write files in allowed directories', }, database: { type: 'stdio', command: 'node', args: ['db.js'], serverInstructions: 'Be careful with database operations', }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.formatInstructionsForContext(['github', 'database']); expect(result).toContain('## github MCP Server Instructions'); expect(result).toContain('Use GitHub API with care'); expect(result).toContain('## database MCP Server Instructions'); expect(result).toContain('Be careful with database operations'); expect(result).not.toContain('files'); expect(result).not.toContain('Only read/write files in allowed directories'); }); it('should handle servers with null or undefined instructions', async () => { (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ github: { type: 'sse', url: 'https://api.github.com', serverInstructions: 'Use GitHub API with care', }, files: { type: 'stdio', command: 'node', args: ['files.js'], serverInstructions: null, }, database: { type: 'stdio', command: 'node', args: ['db.js'], }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.formatInstructionsForContext(); expect(result).toContain('## github MCP Server Instructions'); expect(result).toContain('Use GitHub API with care'); expect(result).not.toContain('files'); expect(result).not.toContain('database'); }); it('should return empty string when filtered servers have no instructions', async () => { (mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({ github: { type: 'sse', url: 'https://api.github.com', serverInstructions: 'Use GitHub API with care', }, files: { type: 'stdio', command: 'node', args: ['files.js'], }, }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.formatInstructionsForContext(['files']); expect(result).toBe(''); }); }); describe('getServerToolFunctions', () => { it('should catch and handle errors gracefully', async () => { (MCPServerInspector.getToolFunctions as jest.Mock) = jest.fn(() => { throw new Error('Connection failed'); }); mockAppConnections({ has: jest.fn().mockResolvedValue(true), }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.getServerToolFunctions(userId, serverName); expect(result).toBeNull(); expect(mockLogger.warn).toHaveBeenCalledWith( `[getServerToolFunctions] Error getting tool functions for server ${serverName}`, expect.any(Error), ); }); it('should catch synchronous errors from getUserConnections', async () => { (MCPServerInspector.getToolFunctions as jest.Mock) = jest.fn().mockResolvedValue({}); mockAppConnections({ get: jest.fn().mockResolvedValue(null), }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const spy = jest.spyOn(manager, 'getUserConnections').mockImplementation(() => { throw new Error('Failed to get user connections'); }); const result = await manager.getServerToolFunctions(userId, serverName); expect(result).toBeNull(); expect(mockLogger.warn).toHaveBeenCalledWith( `[getServerToolFunctions] Error getting tool functions for server ${serverName}`, expect.any(Error), ); expect(spy).toHaveBeenCalled(); }); it('should return tools successfully when no errors occur', async () => { const expectedTools: t.LCAvailableTools = { [`test_tool_mcp_${serverName}`]: { type: 'function', function: { name: `test_tool_mcp_${serverName}`, description: 'Test tool', parameters: { type: 'object' }, }, }, }; (MCPServerInspector.getToolFunctions as jest.Mock) = jest .fn() .mockResolvedValue(expectedTools); mockAppConnections({ has: jest.fn().mockResolvedValue(true), }); const manager = await MCPManager.createInstance(newMCPServersConfig()); const result = await manager.getServerToolFunctions(userId, serverName); expect(result).toEqual(expectedTools); expect(mockLogger.warn).not.toHaveBeenCalled(); }); it('should include specific server name in error messages', async () => { const specificServerName = 'github_mcp_server'; (MCPServerInspector.getToolFunctions as jest.Mock) = jest.fn(() => { throw new Error('Server specific error'); }); mockAppConnections({ has: jest.fn().mockResolvedValue(true), }); const manager = await MCPManager.createInstance(newMCPServersConfig(specificServerName)); const result = await manager.getServerToolFunctions(userId, specificServerName); expect(result).toBeNull(); expect(mockLogger.warn).toHaveBeenCalledWith( `[getServerToolFunctions] Error getting tool functions for server ${specificServerName}`, expect.any(Error), ); }); }); });