🔌 fix: Shared MCP Server Connection Management (#9822)

- Fixed a bug in reinitMCPServer where a user connection was created for an app-level server whenever this server is reinitialized
- Made MCPManager.getUserConnection to return an error if the connection is app-level
- Add MCPManager.getConnection to return either an app connection or a user connection based on the serverName
- Made MCPManager.appConnections public to avoid unnecessary wrapper methods.
This commit is contained in:
Theo N. Truong 2025-09-26 06:24:36 -06:00 committed by GitHub
parent 4f3683fd9a
commit 3219734b9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 56 additions and 42 deletions

View file

@ -442,10 +442,10 @@ async function getMCPSetupData(userId) {
}
const mcpManager = getMCPManager(userId);
/** @type {ReturnType<MCPManager['getAllConnections']>} */
/** @type {Map<string, import('@librechat/api').MCPConnection>} */
let appConnections = new Map();
try {
appConnections = (await mcpManager.getAllConnections()) || new Map();
appConnections = (await mcpManager.appConnections?.getAll()) || new Map();
} catch (error) {
logger.error(`[MCP][User: ${userId}] Error getting app connections:`, error);
}

View file

@ -123,7 +123,7 @@ describe('tests for the new helper functions used by the MCP connection status e
beforeEach(() => {
mockGetAppConfig = require('./Config').getAppConfig;
mockGetMCPManager.mockReturnValue({
getAllConnections: jest.fn(() => new Map()),
appConnections: { getAll: jest.fn(() => new Map()) },
getUserConnections: jest.fn(() => new Map()),
getOAuthServers: jest.fn(() => new Set()),
});
@ -137,7 +137,7 @@ describe('tests for the new helper functions used by the MCP connection status e
const mockOAuthServers = new Set(['server2']);
const mockMCPManager = {
getAllConnections: jest.fn(() => mockAppConnections),
appConnections: { getAll: jest.fn(() => mockAppConnections) },
getUserConnections: jest.fn(() => mockUserConnections),
getOAuthServers: jest.fn(() => mockOAuthServers),
};
@ -147,7 +147,7 @@ describe('tests for the new helper functions used by the MCP connection status e
expect(mockGetAppConfig).toHaveBeenCalled();
expect(mockGetMCPManager).toHaveBeenCalledWith(mockUserId);
expect(mockMCPManager.getAllConnections).toHaveBeenCalled();
expect(mockMCPManager.appConnections.getAll).toHaveBeenCalled();
expect(mockMCPManager.getUserConnections).toHaveBeenCalledWith(mockUserId);
expect(mockMCPManager.getOAuthServers).toHaveBeenCalled();
@ -168,7 +168,7 @@ describe('tests for the new helper functions used by the MCP connection status e
mockGetAppConfig.mockResolvedValue({ mcpConfig: mockConfig.mcpServers });
const mockMCPManager = {
getAllConnections: jest.fn(() => null),
appConnections: { getAll: jest.fn(() => null) },
getUserConnections: jest.fn(() => null),
getOAuthServers: jest.fn(() => null),
};

View file

@ -29,7 +29,7 @@ async function reinitMCPServer({
flowManager: _flowManager,
}) {
/** @type {MCPConnection | null} */
let userConnection = null;
let connection = null;
/** @type {LCAvailableTools | null} */
let availableTools = null;
/** @type {ReturnType<MCPConnection['fetchTools']> | null} */
@ -50,7 +50,7 @@ async function reinitMCPServer({
});
try {
userConnection = await mcpManager.getUserConnection({
connection = await mcpManager.getConnection({
user,
signal,
forceNew,
@ -70,7 +70,7 @@ async function reinitMCPServer({
logger.info(`[MCP Reinitialize] Successfully established connection for ${serverName}`);
} catch (err) {
logger.info(`[MCP Reinitialize] getUserConnection threw error: ${err.message}`);
logger.info(`[MCP Reinitialize] getConnection threw error: ${err.message}`);
logger.info(
`[MCP Reinitialize] OAuth state - oauthRequired: ${oauthRequired}, oauthUrl: ${oauthUrl ? 'present' : 'null'}`,
);
@ -95,8 +95,8 @@ async function reinitMCPServer({
}
}
if (userConnection && !oauthRequired) {
tools = await userConnection.fetchTools();
if (connection && !oauthRequired) {
tools = await connection.fetchTools();
availableTools = await updateMCPServerTools({
serverName,
tools,
@ -111,7 +111,7 @@ async function reinitMCPServer({
if (oauthRequired) {
return `MCP server '${serverName}' ready for OAuth authentication`;
}
if (userConnection) {
if (connection) {
return `MCP server '${serverName}' reinitialized successfully`;
}
return `Failed to reinitialize MCP server '${serverName}'`;
@ -119,7 +119,7 @@ async function reinitMCPServer({
const result = {
availableTools,
success: Boolean((userConnection && !oauthRequired) || (oauthRequired && oauthUrl)),
success: Boolean((connection && !oauthRequired) || (oauthRequired && oauthUrl)),
message: getResponseMessage(),
oauthRequired,
serverName,