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

426 lines
14 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';
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095) * feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-08-16 20:45:55 -04:00
import type { TokenMethods } from '@librechat/data-schemas';
♻️ 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 type { TUser } from 'librechat-data-provider';
import type { FlowStateManager } from '~/flow/manager';
import type { MCPOAuthTokens } from '~/mcp/oauth';
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095) * feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-08-16 20:45:55 -04:00
import type * as t from '~/mcp/types';
import { MCPConnectionFactory } from '~/mcp/MCPConnectionFactory';
import { MCPConnection } from '~/mcp/connection';
♻️ 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 { MCPOAuthHandler } from '~/mcp/oauth';
import { processMCPEnv } from '~/utils';
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095) * feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-08-16 20:45:55 -04:00
jest.mock('~/mcp/connection');
♻️ 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('~/mcp/oauth');
jest.mock('~/utils');
jest.mock('@librechat/data-schemas', () => ({
logger: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
},
}));
const mockLogger = logger as jest.Mocked<typeof logger>;
const mockProcessMCPEnv = processMCPEnv as jest.MockedFunction<typeof processMCPEnv>;
const mockMCPConnection = MCPConnection as jest.MockedClass<typeof MCPConnection>;
const mockMCPOAuthHandler = MCPOAuthHandler as jest.Mocked<typeof MCPOAuthHandler>;
describe('MCPConnectionFactory', () => {
let mockUser: TUser;
let mockServerConfig: t.MCPOptions;
let mockFlowManager: jest.Mocked<FlowStateManager<MCPOAuthTokens | null>>;
let mockConnectionInstance: jest.Mocked<MCPConnection>;
beforeEach(() => {
jest.clearAllMocks();
mockUser = {
id: 'user123',
email: 'test@example.com',
} as TUser;
mockServerConfig = {
command: 'node',
args: ['server.js'],
initTimeout: 5000,
} as t.MCPOptions;
mockFlowManager = {
createFlow: jest.fn(),
createFlowWithHandler: jest.fn(),
getFlowState: jest.fn(),
⚠️ fix: OAuth Error and Token Expiry Detection and Reporting Improvements (#10922) * fix: create new flows on invalid_grant errors * chore: fix failing test * chore: keep isOAuthError test function in sync with implementation * test: add tests for OAuth error detection on invalid grant errors * test: add tests for creating new flows when token expires * test: add test for flow clean up prior to creation * refactor: consolidate token expiration handling in FlowStateManager - Removed the old token expiration checks and replaced them with a new method, `isTokenExpired`, to streamline the logic. - Introduced `normalizeExpirationTimestamp` to handle timestamp normalization for both seconds and milliseconds. - Updated tests to ensure proper functionality of flow management with token expiration scenarios. * fix: conditionally setup cleanup handlers in FlowStateManager - Updated the FlowStateManager constructor to only call setupCleanupHandlers if the ci parameter is not set, improving flexibility in flow management. * chore: enhance OAuth token refresh logging - Introduced a new method, `processRefreshResponse`, to streamline the processing of token refresh responses from the OAuth server. - Improved logging to provide detailed information about token refresh operations, including whether new tokens were received and if the refresh token was rotated. - Updated existing token handling logic to utilize the new method, ensuring consistency and clarity in token management. * chore: enhance logging for MCP server reinitialization - Updated the logging in the reinitMCPServer function to provide more detailed information about the response, including success status, OAuth requirements, presence of the OAuth URL, and the count of tools involved. This improves the clarity and usefulness of logs for debugging purposes. --------- Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-12 10:51:28 -08:00
deleteFlow: jest.fn().mockResolvedValue(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
} as unknown as jest.Mocked<FlowStateManager<MCPOAuthTokens | null>>;
mockConnectionInstance = {
connect: jest.fn(),
isConnected: jest.fn(),
setOAuthTokens: jest.fn(),
on: jest.fn().mockReturnValue(mockConnectionInstance),
once: jest.fn().mockReturnValue(mockConnectionInstance),
off: jest.fn().mockReturnValue(mockConnectionInstance),
removeListener: jest.fn().mockReturnValue(mockConnectionInstance),
♻️ 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
emit: jest.fn(),
} as unknown as jest.Mocked<MCPConnection>;
mockMCPConnection.mockImplementation(() => mockConnectionInstance);
mockProcessMCPEnv.mockReturnValue(mockServerConfig);
});
describe('static create method', () => {
it('should create a basic connection without OAuth', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig,
};
mockConnectionInstance.isConnected.mockResolvedValue(true);
const connection = await MCPConnectionFactory.create(basicOptions);
expect(connection).toBe(mockConnectionInstance);
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095) * feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-08-16 20:45:55 -04:00
expect(mockProcessMCPEnv).toHaveBeenCalledWith({ options: mockServerConfig });
♻️ 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
expect(mockMCPConnection).toHaveBeenCalledWith({
serverName: 'test-server',
serverConfig: mockServerConfig,
userId: undefined,
oauthTokens: null,
});
expect(mockConnectionInstance.connect).toHaveBeenCalled();
});
it('should create a connection with OAuth', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig,
};
const oauthOptions = {
useOAuth: true as const,
user: mockUser,
flowManager: mockFlowManager,
tokenMethods: {
findToken: jest.fn(),
createToken: jest.fn(),
updateToken: jest.fn(),
deleteTokens: jest.fn(),
},
};
const mockTokens: MCPOAuthTokens = {
access_token: 'access123',
refresh_token: 'refresh123',
token_type: 'Bearer',
obtained_at: Date.now(),
};
mockFlowManager.createFlowWithHandler.mockResolvedValue(mockTokens);
mockConnectionInstance.isConnected.mockResolvedValue(true);
const connection = await MCPConnectionFactory.create(basicOptions, oauthOptions);
expect(connection).toBe(mockConnectionInstance);
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095) * feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-08-16 20:45:55 -04:00
expect(mockProcessMCPEnv).toHaveBeenCalledWith({ options: mockServerConfig, user: mockUser });
♻️ 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
expect(mockMCPConnection).toHaveBeenCalledWith({
serverName: 'test-server',
serverConfig: mockServerConfig,
userId: 'user123',
oauthTokens: mockTokens,
});
});
});
describe('OAuth token handling', () => {
it('should return null when no findToken method is provided', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig,
};
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095) * feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-08-16 20:45:55 -04:00
const oauthOptions: t.OAuthConnectionOptions = {
♻️ 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
useOAuth: true as const,
user: mockUser,
flowManager: mockFlowManager,
tokenMethods: {
🏷️ feat: Request Placeholders for Custom Endpoint & MCP Headers (#9095) * feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
2025-08-16 20:45:55 -04:00
findToken: undefined as unknown as TokenMethods['findToken'],
♻️ 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
createToken: jest.fn(),
updateToken: jest.fn(),
deleteTokens: jest.fn(),
},
};
mockConnectionInstance.isConnected.mockResolvedValue(true);
await MCPConnectionFactory.create(basicOptions, oauthOptions);
expect(mockFlowManager.createFlowWithHandler).not.toHaveBeenCalled();
});
it('should handle token retrieval errors gracefully', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig,
};
const oauthOptions = {
useOAuth: true as const,
user: mockUser,
flowManager: mockFlowManager,
tokenMethods: {
findToken: jest.fn(),
createToken: jest.fn(),
updateToken: jest.fn(),
deleteTokens: jest.fn(),
},
};
mockFlowManager.createFlowWithHandler.mockRejectedValue(new Error('Token fetch failed'));
mockConnectionInstance.isConnected.mockResolvedValue(true);
const connection = await MCPConnectionFactory.create(basicOptions, oauthOptions);
expect(connection).toBe(mockConnectionInstance);
expect(mockMCPConnection).toHaveBeenCalledWith({
serverName: 'test-server',
serverConfig: mockServerConfig,
userId: 'user123',
oauthTokens: null,
});
expect(mockLogger.debug).toHaveBeenCalledWith(
expect.stringContaining('No existing tokens found or error loading tokens'),
expect.any(Error),
);
});
});
describe('OAuth event handling', () => {
it('should handle oauthRequired event for returnOnOAuth scenario', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: {
...mockServerConfig,
url: 'https://api.example.com',
type: 'sse' as const,
} as t.SSEOptions,
};
const oauthOptions = {
useOAuth: true as const,
user: mockUser,
flowManager: mockFlowManager,
returnOnOAuth: true,
oauthStart: jest.fn(),
tokenMethods: {
findToken: jest.fn(),
createToken: jest.fn(),
updateToken: jest.fn(),
deleteTokens: jest.fn(),
},
};
const mockFlowData = {
authorizationUrl: 'https://auth.example.com',
flowId: 'flow123',
flowMetadata: {
serverName: 'test-server',
userId: 'user123',
serverUrl: 'https://api.example.com',
state: 'random-state',
clientInfo: { client_id: 'client123' },
},
};
mockMCPOAuthHandler.initiateOAuthFlow.mockResolvedValue(mockFlowData);
mockFlowManager.createFlow.mockRejectedValue(new Error('Timeout expected'));
mockConnectionInstance.isConnected.mockResolvedValue(false);
let oauthRequiredHandler: (data: Record<string, unknown>) => Promise<void>;
mockConnectionInstance.on.mockImplementation((event, handler) => {
if (event === 'oauthRequired') {
oauthRequiredHandler = handler as (data: Record<string, unknown>) => Promise<void>;
}
return mockConnectionInstance;
});
try {
await MCPConnectionFactory.create(basicOptions, oauthOptions);
} catch {
// Expected to fail due to connection not established
}
expect(oauthRequiredHandler!).toBeDefined();
await oauthRequiredHandler!({ serverUrl: 'https://api.example.com' });
expect(mockMCPOAuthHandler.initiateOAuthFlow).toHaveBeenCalledWith(
'test-server',
'https://api.example.com',
'user123',
{},
♻️ 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
undefined,
);
expect(oauthOptions.oauthStart).toHaveBeenCalledWith('https://auth.example.com');
expect(mockConnectionInstance.emit).toHaveBeenCalledWith(
'oauthFailed',
expect.objectContaining({
message: 'OAuth flow initiated - return early',
}),
);
});
⚠️ fix: OAuth Error and Token Expiry Detection and Reporting Improvements (#10922) * fix: create new flows on invalid_grant errors * chore: fix failing test * chore: keep isOAuthError test function in sync with implementation * test: add tests for OAuth error detection on invalid grant errors * test: add tests for creating new flows when token expires * test: add test for flow clean up prior to creation * refactor: consolidate token expiration handling in FlowStateManager - Removed the old token expiration checks and replaced them with a new method, `isTokenExpired`, to streamline the logic. - Introduced `normalizeExpirationTimestamp` to handle timestamp normalization for both seconds and milliseconds. - Updated tests to ensure proper functionality of flow management with token expiration scenarios. * fix: conditionally setup cleanup handlers in FlowStateManager - Updated the FlowStateManager constructor to only call setupCleanupHandlers if the ci parameter is not set, improving flexibility in flow management. * chore: enhance OAuth token refresh logging - Introduced a new method, `processRefreshResponse`, to streamline the processing of token refresh responses from the OAuth server. - Improved logging to provide detailed information about token refresh operations, including whether new tokens were received and if the refresh token was rotated. - Updated existing token handling logic to utilize the new method, ensuring consistency and clarity in token management. * chore: enhance logging for MCP server reinitialization - Updated the logging in the reinitMCPServer function to provide more detailed information about the response, including success status, OAuth requirements, presence of the OAuth URL, and the count of tools involved. This improves the clarity and usefulness of logs for debugging purposes. --------- Co-authored-by: Danny Avila <danny@librechat.ai>
2025-12-12 10:51:28 -08:00
it('should delete existing flow before creating new OAuth flow to prevent stale codeVerifier', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig,
user: mockUser,
};
const oauthOptions = {
user: mockUser,
useOAuth: true,
returnOnOAuth: true,
oauthStart: jest.fn(),
flowManager: mockFlowManager,
};
const mockFlowData = {
authorizationUrl: 'https://auth.example.com',
flowId: 'user123:test-server',
flowMetadata: {
serverName: 'test-server',
userId: 'user123',
serverUrl: 'https://api.example.com',
state: 'test-state',
codeVerifier: 'new-code-verifier-xyz',
clientInfo: { client_id: 'test-client' },
metadata: {
authorization_endpoint: 'https://auth.example.com/authorize',
token_endpoint: 'https://auth.example.com/token',
issuer: 'https://api.example.com',
},
},
};
mockMCPOAuthHandler.initiateOAuthFlow.mockResolvedValue(mockFlowData);
mockFlowManager.deleteFlow.mockResolvedValue(true);
mockFlowManager.createFlow.mockRejectedValue(new Error('Timeout expected'));
mockConnectionInstance.isConnected.mockResolvedValue(false);
let oauthRequiredHandler: (data: Record<string, unknown>) => Promise<void>;
mockConnectionInstance.on.mockImplementation((event, handler) => {
if (event === 'oauthRequired') {
oauthRequiredHandler = handler as (data: Record<string, unknown>) => Promise<void>;
}
return mockConnectionInstance;
});
try {
await MCPConnectionFactory.create(basicOptions, oauthOptions);
} catch {
// Expected to fail due to connection not established
}
await oauthRequiredHandler!({ serverUrl: 'https://api.example.com' });
// Verify deleteFlow was called with correct parameters
expect(mockFlowManager.deleteFlow).toHaveBeenCalledWith('user123:test-server', 'mcp_oauth');
// Verify deleteFlow was called before createFlow
const deleteCallOrder = mockFlowManager.deleteFlow.mock.invocationCallOrder[0];
const createCallOrder = mockFlowManager.createFlow.mock.invocationCallOrder[0];
expect(deleteCallOrder).toBeLessThan(createCallOrder);
// Verify createFlow was called with fresh metadata
expect(mockFlowManager.createFlow).toHaveBeenCalledWith(
'user123:test-server',
'mcp_oauth',
expect.objectContaining({
codeVerifier: 'new-code-verifier-xyz',
}),
);
});
♻️ 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('connection retry logic', () => {
it('should establish connection successfully', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig, // Use default 5000ms timeout
};
mockConnectionInstance.connect.mockResolvedValue(undefined);
mockConnectionInstance.isConnected.mockResolvedValue(true);
const connection = await MCPConnectionFactory.create(basicOptions);
expect(connection).toBe(mockConnectionInstance);
expect(mockConnectionInstance.connect).toHaveBeenCalledTimes(1);
});
it('should handle OAuth errors during connection attempts', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig,
};
const oauthOptions = {
useOAuth: true as const,
user: mockUser,
flowManager: mockFlowManager,
oauthStart: jest.fn(),
tokenMethods: {
findToken: jest.fn(),
createToken: jest.fn(),
updateToken: jest.fn(),
deleteTokens: jest.fn(),
},
};
const oauthError = new Error('Non-200 status code (401)');
(oauthError as unknown as Record<string, unknown>).isOAuthError = true;
mockConnectionInstance.connect.mockRejectedValue(oauthError);
mockConnectionInstance.isConnected.mockResolvedValue(false);
await expect(MCPConnectionFactory.create(basicOptions, oauthOptions)).rejects.toThrow(
'Non-200 status code (401)',
);
expect(mockLogger.info).toHaveBeenCalledWith(
expect.stringContaining('OAuth required, stopping connection attempts'),
);
});
});
describe('isOAuthError method', () => {
it('should identify OAuth errors by message content', async () => {
const basicOptions = {
serverName: 'test-server',
serverConfig: mockServerConfig,
};
const oauthOptions = {
useOAuth: true as const,
user: mockUser,
flowManager: mockFlowManager,
tokenMethods: {
findToken: jest.fn(),
createToken: jest.fn(),
updateToken: jest.fn(),
deleteTokens: jest.fn(),
},
};
const error401 = new Error('401 Unauthorized');
mockConnectionInstance.connect.mockRejectedValue(error401);
mockConnectionInstance.isConnected.mockResolvedValue(false);
await expect(MCPConnectionFactory.create(basicOptions, oauthOptions)).rejects.toThrow('401');
expect(mockLogger.info).toHaveBeenCalledWith(
expect.stringContaining('OAuth required, stopping connection attempts'),
);
});
});
});