diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index c91b67272..9912f79ae 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -1,5 +1,5 @@ const { logger } = require('@librechat/data-schemas'); -const { webSearchKeys, extractWebSearchEnvVars } = require('@librechat/api'); +const { webSearchKeys, extractWebSearchEnvVars, normalizeHttpError } = require('@librechat/api'); const { getFiles, updateUser, @@ -89,8 +89,8 @@ const updateUserPluginsController = async (req, res) => { if (userPluginsService instanceof Error) { logger.error('[userPluginsService]', userPluginsService); - const { status, message } = userPluginsService; - res.status(status).send({ message }); + const { status, message } = normalizeHttpError(userPluginsService); + return res.status(status).send({ message }); } } @@ -137,7 +137,7 @@ const updateUserPluginsController = async (req, res) => { authService = await updateUserPluginAuth(user.id, keys[i], pluginKey, values[i]); if (authService instanceof Error) { logger.error('[authService]', authService); - ({ status, message } = authService); + ({ status, message } = normalizeHttpError(authService)); } } } else if (action === 'uninstall') { @@ -151,7 +151,7 @@ const updateUserPluginsController = async (req, res) => { `[authService] Error deleting all auth for MCP tool ${pluginKey}:`, authService, ); - ({ status, message } = authService); + ({ status, message } = normalizeHttpError(authService)); } } else { // This handles: @@ -163,7 +163,7 @@ const updateUserPluginsController = async (req, res) => { authService = await deleteUserPluginAuth(user.id, keys[i]); // Deletes by authField name if (authService instanceof Error) { logger.error('[authService] Error deleting specific auth key:', authService); - ({ status, message } = authService); + ({ status, message } = normalizeHttpError(authService)); } } } @@ -193,7 +193,8 @@ const updateUserPluginsController = async (req, res) => { return res.status(status).send(); } - res.status(status).send({ message }); + const normalized = normalizeHttpError({ status, message }); + return res.status(normalized.status).send({ message: normalized.message }); } catch (err) { logger.error('[updateUserPluginsController]', err); return res.status(500).json({ message: 'Something went wrong.' }); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 3c847ade3..e1ec956e9 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -450,7 +450,7 @@ "com_nav_maximize_chat_space": "Maximize chat space", "com_nav_mcp_configure_server": "Configure {{0}}", "com_nav_mcp_status_connecting": "{{0}} - Connecting", - "com_nav_mcp_vars_update_error": "Error updating MCP custom user variables: {{0}}", + "com_nav_mcp_vars_update_error": "Error updating MCP custom user variables", "com_nav_mcp_vars_updated": "MCP custom user variables updated successfully.", "com_nav_modular_chat": "Enable switching Endpoints mid-conversation", "com_nav_my_files": "My Files", diff --git a/packages/api/src/utils/http.ts b/packages/api/src/utils/http.ts new file mode 100644 index 000000000..e9f1d6228 --- /dev/null +++ b/packages/api/src/utils/http.ts @@ -0,0 +1,26 @@ +/** + * Normalizes an error-like object into an HTTP status and message. + * Ensures we always respond with a valid numeric status to avoid UI hangs. + */ +export function normalizeHttpError( + err: Error | { status?: number; message?: string } | unknown, + fallbackStatus = 400, +) { + let status = fallbackStatus; + if (err && typeof err === 'object' && 'status' in err && typeof err.status === 'number') { + status = err.status; + } + + let message = 'An error occurred.'; + if ( + err && + typeof err === 'object' && + 'message' in err && + typeof err.message === 'string' && + err.message.length > 0 + ) { + message = err.message; + } + + return { status, message }; +} diff --git a/packages/api/src/utils/index.ts b/packages/api/src/utils/index.ts index db304d4c0..a513425c7 100644 --- a/packages/api/src/utils/index.ts +++ b/packages/api/src/utils/index.ts @@ -12,3 +12,4 @@ export * from './openid'; export * from './tempChatRetention'; export { default as Tokenizer } from './tokenizer'; export * from './yaml'; +export * from './http';