2025-08-13 09:45:06 -06:00
|
|
|
import { logger } from '@librechat/data-schemas';
|
2026-03-14 21:22:25 -04:00
|
|
|
import type * as t from './types';
|
|
|
|
|
import { MCPServersRegistry } from '~/mcp/registry/MCPServersRegistry';
|
2025-08-16 20:45:55 -04:00
|
|
|
import { MCPConnectionFactory } from '~/mcp/MCPConnectionFactory';
|
2026-03-14 21:22:25 -04:00
|
|
|
import { hasCustomUserVars } from './utils';
|
2025-08-13 09:45:06 -06:00
|
|
|
import { MCPConnection } from './connection';
|
|
|
|
|
|
2026-02-17 22:33:57 -05:00
|
|
|
const CONNECT_CONCURRENCY = 3;
|
|
|
|
|
|
2025-08-13 09:45:06 -06:00
|
|
|
/**
|
|
|
|
|
* Manages MCP connections with lazy loading and reconnection.
|
|
|
|
|
* Maintains a pool of connections and handles connection lifecycle management.
|
2025-11-26 15:11:36 +01:00
|
|
|
* Queries server configurations dynamically from the MCPServersRegistry (single source of truth).
|
|
|
|
|
*
|
|
|
|
|
* Scope-aware: Each repository is tied to a specific owner scope:
|
|
|
|
|
* - ownerId = undefined → manages app-level servers only
|
|
|
|
|
* - ownerId = userId → manages user-level and private servers for that user
|
2025-08-13 09:45:06 -06:00
|
|
|
*/
|
|
|
|
|
export class ConnectionsRepository {
|
|
|
|
|
protected connections: Map<string, MCPConnection> = new Map();
|
2025-08-16 20:45:55 -04:00
|
|
|
protected oauthOpts: t.OAuthConnectionOptions | undefined;
|
2025-11-26 15:11:36 +01:00
|
|
|
private readonly ownerId: string | undefined;
|
2025-08-13 09:45:06 -06:00
|
|
|
|
2025-11-26 15:11:36 +01:00
|
|
|
constructor(ownerId?: string, oauthOpts?: t.OAuthConnectionOptions) {
|
|
|
|
|
this.ownerId = ownerId;
|
2025-08-13 09:45:06 -06:00
|
|
|
this.oauthOpts = oauthOpts;
|
|
|
|
|
}
|
|
|
|
|
|
🪣 fix: Prevent Memory Retention from AsyncLocalStorage Context Propagation (#11942)
* fix: store hide_sequential_outputs before processStream clears config
processStream now clears config.configurable after completion to break
memory retention chains. Save hide_sequential_outputs to a local
variable before calling runAgents so the post-stream filter still works.
* feat: memory diagnostics
* chore: expose garbage collection in backend inspect command
Updated the backend inspect command in package.json to include the --expose-gc flag, enabling garbage collection diagnostics for improved memory management during development.
* chore: update @librechat/agents dependency to version 3.1.52
Bumped the version of @librechat/agents in package.json and package-lock.json to ensure compatibility and access to the latest features and fixes.
* fix: clear heavy config state after processStream to prevent memory leaks
Break the reference chain from LangGraph's internal __pregel_scratchpad
through @langchain/core RunTree.extra[lc:child_config] into the
AsyncLocalStorage context captured by timers and I/O handles.
After stream completion, null out symbol-keyed scratchpad properties
(currentTaskInput), config.configurable, and callbacks. Also call
Graph.clearHeavyState() to release config, signal, content maps,
handler registry, and tool sessions.
* chore: fix imports for memory utils
* chore: add circular dependency check in API build step
Enhanced the backend review workflow to include a check for circular dependencies during the API build process. If a circular dependency is detected, an error message is displayed, and the process exits with a failure status.
* chore: update API build step to include circular dependency detection
Modified the backend review workflow to rename the API package installation step to reflect its new functionality, which now includes detection of circular dependencies during the build process.
* chore: add memory diagnostics option to .env.example
Included a commented-out configuration option for enabling memory diagnostics in the .env.example file, which logs heap and RSS snapshots every 60 seconds when activated.
* chore: remove redundant agentContexts cleanup in disposeClient function
Streamlined the disposeClient function by eliminating duplicate cleanup logic for agentContexts, ensuring efficient memory management during client disposal.
* refactor: move runOutsideTracing utility to utils and update its usage
Refactored the runOutsideTracing function by relocating it to the utils module for better organization. Updated the tool execution handler to utilize the new import, ensuring consistent tracing behavior during tool execution.
* refactor: enhance connection management and diagnostics
Added a method to ConnectionsRepository for retrieving the active connection count. Updated UserConnectionManager to utilize this new method for app connection count reporting. Refined the OAuthReconnectionTracker's getStats method to improve clarity in diagnostics. Introduced a new tracing utility in the utils module to streamline tracing context management. Additionally, added a safeguard in memory diagnostics to prevent unnecessary snapshot collection for very short intervals.
* refactor: enhance tracing utility and add memory diagnostics tests
Refactored the runOutsideTracing function to improve warning logic when the AsyncLocalStorage context is missing. Added tests for memory diagnostics and tracing utilities to ensure proper functionality and error handling. Introduced a new test suite for memory diagnostics, covering snapshot collection and garbage collection behavior.
2026-02-25 17:41:23 -05:00
|
|
|
/** Returns the number of active connections in this repository */
|
|
|
|
|
public getConnectionCount(): number {
|
|
|
|
|
return this.connections.size;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 09:45:06 -06:00
|
|
|
/** Checks whether this repository can connect to a specific server */
|
2025-11-26 15:11:36 +01:00
|
|
|
async has(serverName: string): Promise<boolean> {
|
2025-12-01 00:57:46 +01:00
|
|
|
const config = await MCPServersRegistry.getInstance().getServerConfig(serverName, this.ownerId);
|
2025-11-28 16:07:09 +01:00
|
|
|
const canConnect = !!config && this.isAllowedToConnectToServer(config);
|
|
|
|
|
if (!canConnect) {
|
|
|
|
|
//if connection is no longer possible we attempt to disconnect any leftover connections
|
2025-11-26 15:11:36 +01:00
|
|
|
await this.disconnect(serverName);
|
|
|
|
|
}
|
2025-11-28 16:07:09 +01:00
|
|
|
return canConnect;
|
2025-08-13 09:45:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets or creates a connection for the specified server with lazy loading */
|
2025-11-26 15:11:36 +01:00
|
|
|
async get(serverName: string): Promise<MCPConnection | null> {
|
2025-12-01 00:57:46 +01:00
|
|
|
const serverConfig = await MCPServersRegistry.getInstance().getServerConfig(
|
|
|
|
|
serverName,
|
|
|
|
|
this.ownerId,
|
|
|
|
|
);
|
2025-11-28 16:07:09 +01:00
|
|
|
|
2025-08-13 09:45:06 -06:00
|
|
|
const existingConnection = this.connections.get(serverName);
|
2025-11-28 16:07:09 +01:00
|
|
|
if (!serverConfig || !this.isAllowedToConnectToServer(serverConfig)) {
|
2025-11-26 15:11:36 +01:00
|
|
|
if (existingConnection) {
|
|
|
|
|
await existingConnection.disconnect();
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (existingConnection) {
|
|
|
|
|
// Check if config was cached/updated since connection was created
|
2025-12-04 21:37:23 +01:00
|
|
|
if (serverConfig.updatedAt && existingConnection.isStale(serverConfig.updatedAt)) {
|
2025-11-26 15:11:36 +01:00
|
|
|
logger.info(
|
|
|
|
|
`${this.prefix(serverName)} Existing connection for ${serverName} is outdated. Recreating a new connection.`,
|
|
|
|
|
{
|
|
|
|
|
connectionCreated: new Date(existingConnection.createdAt).toISOString(),
|
2025-12-04 21:37:23 +01:00
|
|
|
configCachedAt: new Date(serverConfig.updatedAt).toISOString(),
|
2025-11-26 15:11:36 +01:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Disconnect stale connection
|
|
|
|
|
await existingConnection.disconnect();
|
|
|
|
|
this.connections.delete(serverName);
|
|
|
|
|
// Fall through to create new connection
|
|
|
|
|
} else if (await existingConnection.isConnected()) {
|
|
|
|
|
return existingConnection;
|
|
|
|
|
} else {
|
|
|
|
|
await this.disconnect(serverName);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-13 09:45:06 -06:00
|
|
|
const connection = await MCPConnectionFactory.create(
|
|
|
|
|
{
|
|
|
|
|
serverName,
|
2025-11-26 15:11:36 +01:00
|
|
|
serverConfig,
|
🗝️ feat: Credential Variables for DB-Sourced MCP Servers (#12044)
* feat: Allow Credential Variables in Headers for DB-sourced MCP Servers
- Removed the hasCustomUserVars check from ToolService.js, directly retrieving userMCPAuthMap.
- Updated MCPConnectionFactory and related classes to include a dbSourced flag for better handling of database-sourced configurations.
- Added integration tests to ensure proper behavior of dbSourced servers, verifying that sensitive placeholders are not resolved while allowing customUserVars.
- Adjusted various MCP-related files to accommodate the new dbSourced logic, ensuring consistent handling across the codebase.
* chore: MCPConnectionFactory Tests with Additional Flow Metadata for typing
- Updated MCPConnectionFactory tests to include new fields in flowMetadata: serverUrl and state.
- Enhanced mockFlowData in multiple test cases to reflect the updated structure, ensuring comprehensive coverage of the OAuth flow scenarios.
- Added authorization_endpoint to metadata in the test setup for improved validation of the OAuth process.
* refactor: Simplify MCPManager Configuration Handling
- Removed unnecessary type assertions and streamlined the retrieval of server configuration in MCPManager.
- Enhanced the handling of OAuth and database-sourced flags for improved clarity and efficiency.
- Updated tests to reflect changes in user object structure and ensure proper processing of MCP environment variables.
* refactor: Optimize User MCP Auth Map Retrieval in ToolService
- Introduced conditional loading of userMCPAuthMap based on the presence of MCP-delimited tools, improving efficiency by avoiding unnecessary calls.
- Updated the loadToolDefinitionsWrapper and loadAgentTools functions to reflect this change, enhancing overall performance and clarity.
* test: Add userMCPAuthMap gating tests in ToolService
- Introduced new tests to validate the logic for determining if MCP tools are present in the agent's tool list.
- Implemented various scenarios to ensure accurate detection of MCP tools, including edge cases for empty, undefined, and null tool lists.
- Enhanced clarity and coverage of the ToolService capability checking logic.
* refactor: Enhance MCP Environment Variable Processing
- Simplified the handling of the dbSourced parameter in the processMCPEnv function.
- Introduced a failsafe mechanism to derive dbSourced from options if not explicitly provided, improving robustness and clarity in MCP environment variable processing.
* refactor: Update Regex Patterns for Credential Placeholders in ServerConfigsDB
- Modified regex patterns to include additional credential/env placeholders that should not be allowed in user-provided configurations.
- Clarified comments to emphasize the security risks associated with credential exfiltration when MCP servers are shared between users.
* chore: field order
* refactor: Clean Up dbSourced Parameter Handling in processMCPEnv
- Reintroduced the failsafe mechanism for deriving the dbSourced parameter from options, ensuring clarity and robustness in MCP environment variable processing.
- Enhanced code readability by maintaining consistent comment structure.
* refactor: Update MCPOptions Type to Include Optional dbId
- Modified the processMCPEnv function to extend the MCPOptions type, allowing for an optional dbId property.
- Simplified the logic for deriving the dbSourced parameter by directly checking the dbId property, enhancing code clarity and maintainability.
2026-03-03 18:02:37 -05:00
|
|
|
dbSourced: !!(serverConfig as t.ParsedServerConfig).dbId,
|
2026-02-11 22:09:58 -05:00
|
|
|
useSSRFProtection: MCPServersRegistry.getInstance().shouldEnableSSRFProtection(),
|
2025-08-13 09:45:06 -06:00
|
|
|
},
|
|
|
|
|
this.oauthOpts,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.connections.set(serverName, connection);
|
|
|
|
|
return connection;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets or creates connections for multiple servers concurrently */
|
|
|
|
|
async getMany(serverNames: string[]): Promise<Map<string, MCPConnection>> {
|
2026-02-17 22:33:57 -05:00
|
|
|
const results: [string, MCPConnection | null][] = [];
|
|
|
|
|
for (let i = 0; i < serverNames.length; i += CONNECT_CONCURRENCY) {
|
|
|
|
|
const batch = serverNames.slice(i, i + CONNECT_CONCURRENCY);
|
|
|
|
|
const batchResults = await Promise.all(
|
|
|
|
|
batch.map(
|
|
|
|
|
async (name): Promise<[string, MCPConnection | null]> => [name, await this.get(name)],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
results.push(...batchResults);
|
|
|
|
|
}
|
|
|
|
|
return new Map(results.filter((v): v is [string, MCPConnection] => v[1] != null));
|
2025-08-13 09:45:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Returns all currently loaded connections without creating new ones */
|
|
|
|
|
async getLoaded(): Promise<Map<string, MCPConnection>> {
|
|
|
|
|
return this.getMany(Array.from(this.connections.keys()));
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 15:11:36 +01:00
|
|
|
/** Gets or creates connections for all configured servers in this repository's scope */
|
2025-08-13 09:45:06 -06:00
|
|
|
async getAll(): Promise<Map<string, MCPConnection>> {
|
2025-11-26 15:11:36 +01:00
|
|
|
//TODO in the future we should use a scoped config getter (APPLevel, UserLevel, Private)
|
2025-11-28 16:07:09 +01:00
|
|
|
//for now the absent config will not throw error
|
2025-12-01 00:57:46 +01:00
|
|
|
const allConfigs = await MCPServersRegistry.getInstance().getAllServerConfigs(this.ownerId);
|
2025-11-26 15:11:36 +01:00
|
|
|
return this.getMany(Object.keys(allConfigs));
|
2025-08-13 09:45:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Disconnects and removes a specific server connection from the pool */
|
2025-11-28 16:07:09 +01:00
|
|
|
async disconnect(serverName: string): Promise<void> {
|
2025-08-13 09:45:06 -06:00
|
|
|
const connection = this.connections.get(serverName);
|
|
|
|
|
if (!connection) return Promise.resolve();
|
|
|
|
|
this.connections.delete(serverName);
|
|
|
|
|
return connection.disconnect().catch((err) => {
|
|
|
|
|
logger.error(`${this.prefix(serverName)} Error disconnecting`, err);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Disconnects all active connections and returns array of disconnect promises */
|
|
|
|
|
disconnectAll(): Promise<void>[] {
|
|
|
|
|
const serverNames = Array.from(this.connections.keys());
|
|
|
|
|
return serverNames.map((serverName) => this.disconnect(serverName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns formatted log prefix for server messages
|
|
|
|
|
protected prefix(serverName: string): string {
|
|
|
|
|
return `[MCP][${serverName}]`;
|
|
|
|
|
}
|
2025-11-28 16:07:09 +01:00
|
|
|
|
2026-03-14 21:22:25 -04:00
|
|
|
/**
|
|
|
|
|
* App-level (shared) connections cannot serve servers that need per-user context:
|
|
|
|
|
* env/header placeholders like `{{MY_KEY}}` are only resolved by `processMCPEnv()`
|
|
|
|
|
* when real `customUserVars` values exist — which requires a user-level connection.
|
|
|
|
|
*/
|
2025-11-28 16:07:09 +01:00
|
|
|
private isAllowedToConnectToServer(config: t.ParsedServerConfig) {
|
🩹 fix: MCP Server Recovery from Startup Inspection Failures (#12145)
* feat: MCP server reinitialization recovery mechanism
- Added functionality to store a stub configuration for MCP servers that fail inspection at startup, allowing for recovery via reinitialization.
- Introduced `reinspectServer` method in `MCPServersRegistry` to handle reinspection of previously failed servers.
- Enhanced `MCPServersInitializer` to log and manage server initialization failures, ensuring proper handling of inspection failures.
- Added integration tests to verify the recovery process for unreachable MCP servers, ensuring that stub configurations are stored and can be reinitialized successfully.
- Updated type definitions to include `inspectionFailed` flag in server configurations for better state management.
* fix: MCP server handling for inspection failures
- Updated `reinitMCPServer` to return a structured response when the server is unreachable, providing clearer feedback on the failure.
- Modified `ConnectionsRepository` to prevent connections to servers marked as inspection failed, improving error handling.
- Adjusted `MCPServersRegistry` methods to ensure proper management of server states, including throwing errors for non-failed servers during reinspection.
- Enhanced integration tests to validate the behavior of the system when dealing with unreachable MCP servers and inspection failures, ensuring robust recovery mechanisms.
* fix: Clear all cached server configurations in MCPServersRegistry
- Added a comment to clarify the necessity of clearing all cached server configurations when updating a server's configuration, as the cache is keyed by userId without a reverse index for enumeration.
* fix: Update integration test for file_tools_server inspection handling
- Modified the test to verify that the `file_tools_server` is stored as a stub when inspection fails, ensuring it can be reinitialized correctly.
- Adjusted expectations to confirm that the `inspectionFailed` flag is set to true for the stub configuration, enhancing the robustness of the recovery mechanism.
* test: Add unit tests for reinspecting servers in MCPServersRegistry
- Introduced tests for the `reinspectServer` method to validate error handling when called on a healthy server and when the server does not exist.
- Ensured that appropriate exceptions are thrown for both scenarios, enhancing the robustness of server state management.
* test: Add integration test for concurrent reinspectServer calls
- Introduced a new test to validate that multiple concurrent calls to reinspectServer do not crash or corrupt the server state.
- Ensured that at least one call succeeds and any failures are due to the server not being in a failed state, enhancing the reliability of the reinitialization process.
* test: Enhance integration test for concurrent MCP server reinitialization
- Added a new test to validate that concurrent calls to reinitialize the MCP server do not crash or corrupt the server state.
- Ensured that at least one call succeeds and that failures are handled gracefully, improving the reliability of the reinitialization process.
- Reset MCPManager instance after each test to maintain a clean state for subsequent tests.
2026-03-08 21:49:04 -04:00
|
|
|
if (config.inspectionFailed) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-14 21:22:25 -04:00
|
|
|
if (
|
|
|
|
|
this.ownerId === undefined &&
|
|
|
|
|
(config.startup === false || config.requiresOAuth || hasCustomUserVars(config))
|
|
|
|
|
) {
|
2025-11-28 16:07:09 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-08-13 09:45:06 -06:00
|
|
|
}
|