mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
📡 refactor: MCP Runtime Config Sync with Redis Distributed Locking (#10352)
* 🔄 Refactoring: MCP Runtime Configuration Reload
- PrivateServerConfigs own cache classes (inMemory and Redis).
- Connections staleness detection by comparing (connection.createdAt and config.LastUpdatedAt)
- ConnectionsRepo access Registry instead of in memory config dict and renew stale connections
- MCPManager: adjusted init of ConnectionsRepo (app level)
- UserConnectionManager: renew stale connections
- skipped test, to test "should only clear keys in its own namespace"
- MCPPrivateServerLoader: new component to manage logic of loading / editing private servers on runtime
- PrivateServersLoadStatusCache to track private server cache status
- New unit and integration tests.
Misc:
- add es lint rule to enforce line between class methods
* Fix cluster mode batch update and delete workarround. Fixed unit tests for cluster mode.
* Fix Keyv redis clear cache namespace awareness issue + Integration tests fixes
* chore: address copilot comments
* Fixing rebase issue: removed the mcp config fallback in single getServerConfig method:
- to not to interfere with the logic of the right Tier (APP/USER/Private)
- If userId is null, the getServerConfig should not return configs that are a SharedUser tier and not APP tier
* chore: add dev-staging branch to workflow triggers for backend, cache integration, and ESLint checks
---------
Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
This commit is contained in:
parent
52e6796635
commit
ac68e629e6
49 changed files with 5244 additions and 257 deletions
115
packages/api/src/mcp/registry/cache/PrivateServerConfigs/PrivateServerConfigsCacheBase.ts
vendored
Normal file
115
packages/api/src/mcp/registry/cache/PrivateServerConfigs/PrivateServerConfigsCacheBase.ts
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import type * as t from '~/mcp/types';
|
||||
import { ServerConfigsCache, ServerConfigsCacheFactory } from '../ServerConfigsCacheFactory';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
|
||||
export abstract class PrivateServerConfigsCacheBase {
|
||||
protected readonly PREFIX = 'MCP::ServersRegistry::Servers::Private';
|
||||
protected caches: Map<string, ServerConfigsCache> = new Map();
|
||||
|
||||
public async add(
|
||||
userId: string,
|
||||
serverName: string,
|
||||
config: t.ParsedServerConfig,
|
||||
): Promise<void> {
|
||||
const userCache = this.getOrCreatePrivateUserCache(userId);
|
||||
await userCache.add(serverName, config);
|
||||
}
|
||||
|
||||
public async update(
|
||||
userId: string,
|
||||
serverName: string,
|
||||
config: t.ParsedServerConfig,
|
||||
): Promise<void> {
|
||||
const userCache = this.getOrCreatePrivateUserCache(userId);
|
||||
await userCache.update(serverName, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific server config from a user's cache.
|
||||
*/
|
||||
public async get(userId: string, serverName: string): Promise<t.ParsedServerConfig | undefined> {
|
||||
const cache = this.getOrCreatePrivateUserCache(userId);
|
||||
return await cache.get(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all server configs for a user.
|
||||
*/
|
||||
public async getAll(userId: string): Promise<Record<string, t.ParsedServerConfig>> {
|
||||
const cache = this.getOrCreatePrivateUserCache(userId);
|
||||
return await cache.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has a cache instance loaded.
|
||||
*/
|
||||
public abstract has(userId: string): Promise<boolean>;
|
||||
|
||||
public async remove(userId: string, serverName: string): Promise<void> {
|
||||
const userCache = this.getOrCreatePrivateUserCache(userId);
|
||||
await userCache.remove(serverName);
|
||||
}
|
||||
|
||||
public async reset(userId: string): Promise<void> {
|
||||
const cache = this.getOrCreatePrivateUserCache(userId);
|
||||
return cache.reset();
|
||||
}
|
||||
|
||||
// ============= BATCH OPERATION PRIMITIVES =============
|
||||
// Simple primitives for MCPPrivateServerLoader orchestration - no business logic
|
||||
|
||||
/**
|
||||
* Update server config in ALL user caches that already have it.
|
||||
* Efficient: Uses pattern-based scan, skips users who don't have it.
|
||||
* Use case: Metadata changed (command, args, env)
|
||||
*/
|
||||
public abstract updateServerConfigIfExists(
|
||||
serverName: string,
|
||||
config: t.ParsedServerConfig,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Add server config ONLY to users whose caches are already initialized.
|
||||
* Skips users without initialized caches (doesn't create new caches).
|
||||
* Use case: Granting access to existing users
|
||||
*/
|
||||
public abstract addServerConfigIfCacheExists(
|
||||
userIds: string[],
|
||||
serverName: string,
|
||||
config: t.ParsedServerConfig,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove server config ONLY from users whose caches exist.
|
||||
* Ignores users without initialized caches.
|
||||
* Use case: Revoking access from users
|
||||
*/
|
||||
public abstract removeServerConfigIfCacheExists(
|
||||
userIds: string[],
|
||||
serverName: string,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Find all users who have this server in their cache.
|
||||
* Primitive for determining affected users.
|
||||
*/
|
||||
public abstract findUsersWithServer(serverName: string): Promise<string[]>;
|
||||
|
||||
/**
|
||||
* Clear all private server configs for all users (nuclear option).
|
||||
* Use sparingly - typically only for testing or full reset.
|
||||
*/
|
||||
public abstract resetAll(): Promise<void>;
|
||||
|
||||
protected getOrCreatePrivateUserCache(userId: string): ServerConfigsCache {
|
||||
if (!userId) {
|
||||
logger.error('userId is required to get or create private user cache');
|
||||
throw new Error('userId is required to get or create private user cache');
|
||||
}
|
||||
if (!this.caches.has(userId)) {
|
||||
const cache = ServerConfigsCacheFactory.create(userId, 'Private', false);
|
||||
this.caches.set(userId, cache);
|
||||
}
|
||||
return this.caches.get(userId)!;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue