2025-08-23 03:27:05 -04:00
|
|
|
const { logger } = require('@librechat/data-schemas');
|
|
|
|
|
const { CacheKeys, Constants } = require('librechat-data-provider');
|
|
|
|
|
const { findToken, createToken, updateToken, deleteTokens } = require('~/models');
|
|
|
|
|
const { getMCPManager, getFlowStateManager } = require('~/config');
|
2025-09-21 07:56:40 -04:00
|
|
|
const { updateMCPServerTools } = require('~/server/services/Config');
|
2025-08-23 03:27:05 -04:00
|
|
|
const { getLogStores } = require('~/cache');
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Object} params
|
2025-09-24 22:48:38 -04:00
|
|
|
* @param {IUser} params.user - The user from the request object.
|
2025-08-23 03:27:05 -04:00
|
|
|
* @param {string} params.serverName - The name of the MCP server
|
|
|
|
|
* @param {boolean} params.returnOnOAuth - Whether to initiate OAuth and return, or wait for OAuth flow to finish
|
|
|
|
|
* @param {AbortSignal} [params.signal] - The abort signal to handle cancellation.
|
|
|
|
|
* @param {boolean} [params.forceNew]
|
|
|
|
|
* @param {number} [params.connectionTimeout]
|
|
|
|
|
* @param {FlowStateManager<any>} [params.flowManager]
|
|
|
|
|
* @param {(authURL: string) => Promise<boolean>} [params.oauthStart]
|
|
|
|
|
* @param {Record<string, Record<string, string>>} [params.userMCPAuthMap]
|
|
|
|
|
*/
|
|
|
|
|
async function reinitMCPServer({
|
2025-09-24 22:48:38 -04:00
|
|
|
user,
|
2025-08-23 03:27:05 -04:00
|
|
|
signal,
|
|
|
|
|
forceNew,
|
|
|
|
|
serverName,
|
|
|
|
|
userMCPAuthMap,
|
|
|
|
|
connectionTimeout,
|
|
|
|
|
returnOnOAuth = true,
|
|
|
|
|
oauthStart: _oauthStart,
|
|
|
|
|
flowManager: _flowManager,
|
|
|
|
|
}) {
|
|
|
|
|
/** @type {MCPConnection | null} */
|
2025-09-26 06:24:36 -06:00
|
|
|
let connection = null;
|
2025-08-23 03:27:05 -04:00
|
|
|
/** @type {LCAvailableTools | null} */
|
|
|
|
|
let availableTools = null;
|
|
|
|
|
/** @type {ReturnType<MCPConnection['fetchTools']> | null} */
|
|
|
|
|
let tools = null;
|
|
|
|
|
let oauthRequired = false;
|
|
|
|
|
let oauthUrl = null;
|
|
|
|
|
try {
|
|
|
|
|
const customUserVars = userMCPAuthMap?.[`${Constants.mcp_prefix}${serverName}`];
|
|
|
|
|
const flowManager = _flowManager ?? getFlowStateManager(getLogStores(CacheKeys.FLOWS));
|
|
|
|
|
const mcpManager = getMCPManager();
|
|
|
|
|
|
|
|
|
|
const oauthStart =
|
|
|
|
|
_oauthStart ??
|
|
|
|
|
(async (authURL) => {
|
2025-09-19 06:50:02 -04:00
|
|
|
logger.info(`[MCP Reinitialize] OAuth URL received for ${serverName}`);
|
2025-08-23 03:27:05 -04:00
|
|
|
oauthUrl = authURL;
|
|
|
|
|
oauthRequired = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
2025-09-26 06:24:36 -06:00
|
|
|
connection = await mcpManager.getConnection({
|
2025-09-24 22:48:38 -04:00
|
|
|
user,
|
2025-08-23 03:27:05 -04:00
|
|
|
signal,
|
|
|
|
|
forceNew,
|
|
|
|
|
oauthStart,
|
|
|
|
|
serverName,
|
|
|
|
|
flowManager,
|
|
|
|
|
returnOnOAuth,
|
|
|
|
|
customUserVars,
|
|
|
|
|
connectionTimeout,
|
|
|
|
|
tokenMethods: {
|
|
|
|
|
findToken,
|
|
|
|
|
updateToken,
|
|
|
|
|
createToken,
|
|
|
|
|
deleteTokens,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info(`[MCP Reinitialize] Successfully established connection for ${serverName}`);
|
|
|
|
|
} catch (err) {
|
2025-09-26 06:24:36 -06:00
|
|
|
logger.info(`[MCP Reinitialize] getConnection threw error: ${err.message}`);
|
2025-08-23 03:27:05 -04:00
|
|
|
logger.info(
|
|
|
|
|
`[MCP Reinitialize] OAuth state - oauthRequired: ${oauthRequired}, oauthUrl: ${oauthUrl ? 'present' : 'null'}`,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const isOAuthError =
|
|
|
|
|
err.message?.includes('OAuth') ||
|
|
|
|
|
err.message?.includes('authentication') ||
|
|
|
|
|
err.message?.includes('401');
|
|
|
|
|
|
|
|
|
|
const isOAuthFlowInitiated = err.message === 'OAuth flow initiated - return early';
|
|
|
|
|
|
|
|
|
|
if (isOAuthError || oauthRequired || isOAuthFlowInitiated) {
|
|
|
|
|
logger.info(
|
|
|
|
|
`[MCP Reinitialize] OAuth required for ${serverName} (isOAuthError: ${isOAuthError}, oauthRequired: ${oauthRequired}, isOAuthFlowInitiated: ${isOAuthFlowInitiated})`,
|
|
|
|
|
);
|
|
|
|
|
oauthRequired = true;
|
|
|
|
|
} else {
|
|
|
|
|
logger.error(
|
|
|
|
|
`[MCP Reinitialize] Error initializing MCP server ${serverName} for user:`,
|
|
|
|
|
err,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 06:24:36 -06:00
|
|
|
if (connection && !oauthRequired) {
|
|
|
|
|
tools = await connection.fetchTools();
|
2025-09-21 07:56:40 -04:00
|
|
|
availableTools = await updateMCPServerTools({
|
2025-10-30 22:09:56 +01:00
|
|
|
userId: user.id,
|
2025-08-23 03:27:05 -04:00
|
|
|
serverName,
|
|
|
|
|
tools,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug(
|
|
|
|
|
`[MCP Reinitialize] Sending response for ${serverName} - oauthRequired: ${oauthRequired}, oauthUrl: ${oauthUrl ? 'present' : 'null'}`,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const getResponseMessage = () => {
|
|
|
|
|
if (oauthRequired) {
|
|
|
|
|
return `MCP server '${serverName}' ready for OAuth authentication`;
|
|
|
|
|
}
|
2025-09-26 06:24:36 -06:00
|
|
|
if (connection) {
|
2025-08-23 03:27:05 -04:00
|
|
|
return `MCP server '${serverName}' reinitialized successfully`;
|
|
|
|
|
}
|
|
|
|
|
return `Failed to reinitialize MCP server '${serverName}'`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = {
|
|
|
|
|
availableTools,
|
2025-09-26 06:24:36 -06:00
|
|
|
success: Boolean((connection && !oauthRequired) || (oauthRequired && oauthUrl)),
|
2025-08-23 03:27:05 -04:00
|
|
|
message: getResponseMessage(),
|
|
|
|
|
oauthRequired,
|
|
|
|
|
serverName,
|
|
|
|
|
oauthUrl,
|
|
|
|
|
tools,
|
|
|
|
|
};
|
⚠️ 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
|
|
|
logger.debug(`[MCP Reinitialize] Response for ${serverName}:`, {
|
|
|
|
|
success: result.success,
|
|
|
|
|
oauthRequired: result.oauthRequired,
|
|
|
|
|
oauthUrl: result.oauthUrl ? 'present' : null,
|
|
|
|
|
toolsCount: tools?.length ?? 0,
|
|
|
|
|
});
|
2025-08-23 03:27:05 -04:00
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error(
|
|
|
|
|
'[MCP Reinitialize] Error loading MCP Tools, servers may still be initializing:',
|
|
|
|
|
error,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
reinitMCPServer,
|
|
|
|
|
};
|