🧬 refactor: Wire Database Methods into MCP Package via Registry Pattern (#10715)

* Refactor: MCPServersRegistry Singleton Pattern with Dependency Injection for DB methods consumption

* refactor: error handling in MCP initialization and improve logging for MCPServersRegistry instance creation.

- Added checks for mongoose instance in ServerConfigsDB constructor and refined error messages for clarity.
- Reorder and use type imports

---------

Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Atef Bellaaj 2025-12-01 00:57:46 +01:00 committed by Danny Avila
parent da473bf43a
commit ad6ba4b6d1
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
24 changed files with 328 additions and 150 deletions

View file

@ -21,18 +21,21 @@ jest.mock('../MCPConnectionFactory', () => ({
jest.mock('../connection');
// Mock the registry
const mockRegistryInstance = {
getServerConfig: jest.fn(),
getAllServerConfigs: jest.fn(),
};
jest.mock('../registry/MCPServersRegistry', () => ({
mcpServersRegistry: {
getServerConfig: jest.fn(),
getAllServerConfigs: jest.fn(),
MCPServersRegistry: {
getInstance: () => mockRegistryInstance,
},
}));
const mockLogger = logger as jest.Mocked<typeof logger>;
// Import mocked registry
import { mcpServersRegistry as registry } from '../registry/MCPServersRegistry';
const mockRegistry = registry as jest.Mocked<typeof registry>;
// Use mocked registry instance
const mockRegistry = mockRegistryInstance as jest.Mocked<typeof mockRegistryInstance>;
describe('ConnectionsRepository', () => {
let repository: ConnectionsRepository;

View file

@ -1,7 +1,6 @@
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';
@ -17,11 +16,15 @@ jest.mock('@librechat/data-schemas', () => ({
},
}));
const mockRegistryInstance = {
getServerConfig: jest.fn(),
getAllServerConfigs: jest.fn(),
getOAuthServers: jest.fn(),
};
jest.mock('~/mcp/registry/MCPServersRegistry', () => ({
mcpServersRegistry: {
getServerConfig: jest.fn(),
getAllServerConfigs: jest.fn(),
getOAuthServers: jest.fn(),
MCPServersRegistry: {
getInstance: () => mockRegistryInstance,
},
}));
@ -47,7 +50,7 @@ describe('MCPManager', () => {
// Set up default mock implementations
(MCPServersInitializer.initialize as jest.Mock).mockResolvedValue(undefined);
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({});
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({});
});
function mockAppConnections(
@ -75,7 +78,7 @@ describe('MCPManager', () => {
describe('getAppToolFunctions', () => {
it('should return empty object when no servers have tool functions', async () => {
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
server1: { type: 'stdio', command: 'test', args: [] },
server2: { type: 'stdio', command: 'test2', args: [] },
});
@ -109,7 +112,7 @@ describe('MCPManager', () => {
},
};
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
server1: {
type: 'stdio',
command: 'test',
@ -145,7 +148,7 @@ describe('MCPManager', () => {
},
};
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
server1: {
type: 'stdio',
command: 'test',
@ -174,7 +177,7 @@ describe('MCPManager', () => {
describe('formatInstructionsForContext', () => {
it('should return empty string when no servers have instructions', async () => {
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
server1: { type: 'stdio', command: 'test', args: [] },
server2: { type: 'stdio', command: 'test2', args: [] },
});
@ -186,7 +189,7 @@ describe('MCPManager', () => {
});
it('should format instructions from multiple servers', async () => {
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
github: {
type: 'sse',
url: 'https://api.github.com',
@ -211,7 +214,7 @@ describe('MCPManager', () => {
});
it('should filter instructions by server names when provided', async () => {
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
github: {
type: 'sse',
url: 'https://api.github.com',
@ -243,7 +246,7 @@ describe('MCPManager', () => {
});
it('should handle servers with null or undefined instructions', async () => {
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
github: {
type: 'sse',
url: 'https://api.github.com',
@ -272,7 +275,7 @@ describe('MCPManager', () => {
});
it('should return empty string when filtered servers have no instructions', async () => {
(mcpServersRegistry.getAllServerConfigs as jest.Mock).mockResolvedValue({
(mockRegistryInstance.getAllServerConfigs as jest.Mock).mockResolvedValue({
github: {
type: 'sse',
url: 'https://api.github.com',