mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
🔑 refactor: MCP Settings Rendering Logic for OAuth Servers (#8718)
* feat: add OAuth servers to conditional rendering logic for MCPPanel in SideNav * feat: add startup flag check to conditional rendering logic * fix: correct improper handling of failure state in reinitialize endpoint * fix: change MCP config components to better handle servers without customUserVars - removes the subtle reinitialize button from config components of servers without customUserVars or OAuth - adds a placeholder message for components where servers have no customUserVars configured * style: swap CustomUserVarsSection and ServerInitializationSection positions * style: fix coloring for light mode and align more with existing design patterns * chore: remove extraneous comments * chore: reorder imports and `isEnabled` from api package --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
ef9d9b1276
commit
c4677ab3fb
10 changed files with 68 additions and 33 deletions
|
|
@ -1,10 +1,11 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const { isEnabled } = require('@librechat/api');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
|
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
|
||||||
const { getCustomConfig } = require('~/server/services/Config/getCustomConfig');
|
const { getCustomConfig } = require('~/server/services/Config/getCustomConfig');
|
||||||
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
||||||
const { getProjectByName } = require('~/models/Project');
|
const { getProjectByName } = require('~/models/Project');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { getMCPManager } = require('~/config');
|
||||||
const { getLogStores } = require('~/cache');
|
const { getLogStores } = require('~/cache');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
@ -102,11 +103,16 @@ router.get('/', async function (req, res) {
|
||||||
payload.mcpServers = {};
|
payload.mcpServers = {};
|
||||||
const config = await getCustomConfig();
|
const config = await getCustomConfig();
|
||||||
if (config?.mcpServers != null) {
|
if (config?.mcpServers != null) {
|
||||||
|
const mcpManager = getMCPManager();
|
||||||
|
const oauthServers = mcpManager.getOAuthServers();
|
||||||
|
|
||||||
for (const serverName in config.mcpServers) {
|
for (const serverName in config.mcpServers) {
|
||||||
const serverConfig = config.mcpServers[serverName];
|
const serverConfig = config.mcpServers[serverName];
|
||||||
payload.mcpServers[serverName] = {
|
payload.mcpServers[serverName] = {
|
||||||
customUserVars: serverConfig?.customUserVars || {},
|
customUserVars: serverConfig?.customUserVars || {},
|
||||||
chatMenu: serverConfig?.chatMenu,
|
chatMenu: serverConfig?.chatMenu,
|
||||||
|
isOAuth: oauthServers.has(serverName),
|
||||||
|
startup: serverConfig?.startup,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -452,11 +452,19 @@ router.post('/:serverName/reinitialize', requireJwtAuth, async (req, res) => {
|
||||||
`[MCP Reinitialize] Sending response for ${serverName} - oauthRequired: ${oauthRequired}, oauthUrl: ${oauthUrl ? 'present' : 'null'}`,
|
`[MCP Reinitialize] Sending response for ${serverName} - oauthRequired: ${oauthRequired}, oauthUrl: ${oauthUrl ? 'present' : 'null'}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getResponseMessage = () => {
|
||||||
|
if (oauthRequired) {
|
||||||
|
return `MCP server '${serverName}' ready for OAuth authentication`;
|
||||||
|
}
|
||||||
|
if (userConnection) {
|
||||||
|
return `MCP server '${serverName}' reinitialized successfully`;
|
||||||
|
}
|
||||||
|
return `Failed to reinitialize MCP server '${serverName}'`;
|
||||||
|
};
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: userConnection && !oauthRequired,
|
||||||
message: oauthRequired
|
message: getResponseMessage(),
|
||||||
? `MCP server '${serverName}' ready for OAuth authentication`
|
|
||||||
: `MCP server '${serverName}' reinitialized successfully`,
|
|
||||||
serverName,
|
serverName,
|
||||||
oauthRequired,
|
oauthRequired,
|
||||||
oauthUrl,
|
oauthUrl,
|
||||||
|
|
|
||||||
|
|
@ -110,13 +110,15 @@ export default function CustomUserVarsSection({
|
||||||
|
|
||||||
const handleRevokeClick = () => {
|
const handleRevokeClick = () => {
|
||||||
onRevoke();
|
onRevoke();
|
||||||
// Reset form after revoke
|
|
||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't render if no fields to configure
|
|
||||||
if (!fields || Object.keys(fields).length === 0) {
|
if (!fields || Object.keys(fields).length === 0) {
|
||||||
return null;
|
return (
|
||||||
|
<div className="p-4 text-center text-sm text-gray-500">
|
||||||
|
{localize('com_sidepanel_mcp_no_custom_vars', { '0': serverName })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ export default function MCPConfigDialog({
|
||||||
<ServerInitializationSection
|
<ServerInitializationSection
|
||||||
serverName={serverName}
|
serverName={serverName}
|
||||||
requiresOAuth={serverStatus?.requiresOAuth || false}
|
requiresOAuth={serverStatus?.requiresOAuth || false}
|
||||||
|
hasCustomUserVars={fieldsSchema && Object.keys(fieldsSchema).length > 0}
|
||||||
/>
|
/>
|
||||||
</OGDialogContent>
|
</OGDialogContent>
|
||||||
</OGDialog>
|
</OGDialog>
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ import { useLocalize } from '~/hooks';
|
||||||
interface ServerInitializationSectionProps {
|
interface ServerInitializationSectionProps {
|
||||||
serverName: string;
|
serverName: string;
|
||||||
requiresOAuth: boolean;
|
requiresOAuth: boolean;
|
||||||
|
hasCustomUserVars?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ServerInitializationSection({
|
export default function ServerInitializationSection({
|
||||||
serverName,
|
serverName,
|
||||||
requiresOAuth,
|
requiresOAuth,
|
||||||
|
hasCustomUserVars = false,
|
||||||
}: ServerInitializationSectionProps) {
|
}: ServerInitializationSectionProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
|
|
@ -39,8 +41,7 @@ export default function ServerInitializationSection({
|
||||||
cancelOAuthFlow(serverName);
|
cancelOAuthFlow(serverName);
|
||||||
}, [cancelOAuthFlow, serverName]);
|
}, [cancelOAuthFlow, serverName]);
|
||||||
|
|
||||||
// Show subtle reinitialize option if connected
|
if (isConnected && (requiresOAuth || hasCustomUserVars)) {
|
||||||
if (isConnected) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<button
|
<button
|
||||||
|
|
@ -55,11 +56,15 @@ export default function ServerInitializationSection({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-[#991b1b] bg-[#2C1315] p-4">
|
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-700 dark:bg-amber-900/20">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-medium text-red-700 dark:text-red-300">
|
<span className="text-sm font-medium text-amber-800 dark:text-amber-200">
|
||||||
{requiresOAuth
|
{requiresOAuth
|
||||||
? localize('com_ui_mcp_not_authenticated', { 0: serverName })
|
? localize('com_ui_mcp_not_authenticated', { 0: serverName })
|
||||||
: localize('com_ui_mcp_not_initialized', { 0: serverName })}
|
: localize('com_ui_mcp_not_initialized', { 0: serverName })}
|
||||||
|
|
@ -70,7 +75,7 @@ export default function ServerInitializationSection({
|
||||||
<Button
|
<Button
|
||||||
onClick={handleInitializeClick}
|
onClick={handleInitializeClick}
|
||||||
disabled={isServerInitializing}
|
disabled={isServerInitializing}
|
||||||
className="flex items-center gap-2 bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:hover:bg-blue-800"
|
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
||||||
>
|
>
|
||||||
{isServerInitializing ? (
|
{isServerInitializing ? (
|
||||||
<>
|
<>
|
||||||
|
|
@ -103,7 +108,7 @@ export default function ServerInitializationSection({
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => window.open(serverOAuthUrl, '_blank', 'noopener,noreferrer')}
|
onClick={() => window.open(serverOAuthUrl, '_blank', 'noopener,noreferrer')}
|
||||||
className="flex-1 bg-blue-600 text-white hover:bg-blue-700 dark:hover:bg-blue-800"
|
className="flex-1 bg-green-600 text-white hover:bg-green-700 dark:hover:bg-green-800"
|
||||||
>
|
>
|
||||||
{localize('com_ui_continue_oauth')}
|
{localize('com_ui_continue_oauth')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -141,29 +141,31 @@ function MCPPanelContent() {
|
||||||
{localize('com_sidepanel_mcp_variables_for', { '0': serverBeingEdited.serverName })}
|
{localize('com_sidepanel_mcp_variables_for', { '0': serverBeingEdited.serverName })}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Server Initialization Section */}
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<ServerInitializationSection
|
<CustomUserVarsSection
|
||||||
serverName={selectedServerNameForEditing}
|
serverName={selectedServerNameForEditing}
|
||||||
requiresOAuth={serverStatus?.requiresOAuth || false}
|
fields={serverBeingEdited.config.customUserVars}
|
||||||
|
onSave={(authData) => {
|
||||||
|
if (selectedServerNameForEditing) {
|
||||||
|
handleConfigSave(selectedServerNameForEditing, authData);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onRevoke={() => {
|
||||||
|
if (selectedServerNameForEditing) {
|
||||||
|
handleConfigRevoke(selectedServerNameForEditing);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
isSubmitting={updateUserPluginsMutation.isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom User Variables Section */}
|
<ServerInitializationSection
|
||||||
<CustomUserVarsSection
|
|
||||||
serverName={selectedServerNameForEditing}
|
serverName={selectedServerNameForEditing}
|
||||||
fields={serverBeingEdited.config.customUserVars}
|
requiresOAuth={serverStatus?.requiresOAuth || false}
|
||||||
onSave={(authData) => {
|
hasCustomUserVars={
|
||||||
if (selectedServerNameForEditing) {
|
serverBeingEdited.config.customUserVars &&
|
||||||
handleConfigSave(selectedServerNameForEditing, authData);
|
Object.keys(serverBeingEdited.config.customUserVars).length > 0
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
onRevoke={() => {
|
|
||||||
if (selectedServerNameForEditing) {
|
|
||||||
handleConfigRevoke(selectedServerNameForEditing);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
isSubmitting={updateUserPluginsMutation.isLoading}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,12 @@ export function useMCPServerManager() {
|
||||||
|
|
||||||
cleanupServerState(serverName);
|
cleanupServerState(serverName);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_mcp_init_failed', { 0: serverName }),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
cleanupServerState(serverName);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[MCP Manager] Failed to initialize ${serverName}:`, error);
|
console.error(`[MCP Manager] Failed to initialize ${serverName}:`, error);
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,10 @@ export default function useSideNavLinks({
|
||||||
if (
|
if (
|
||||||
startupConfig?.mcpServers &&
|
startupConfig?.mcpServers &&
|
||||||
Object.values(startupConfig.mcpServers).some(
|
Object.values(startupConfig.mcpServers).some(
|
||||||
(server) => server.customUserVars && Object.keys(server.customUserVars).length > 0,
|
(server: any) =>
|
||||||
|
(server.customUserVars && Object.keys(server.customUserVars).length > 0) ||
|
||||||
|
server.isOAuth ||
|
||||||
|
server.startup === false,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
links.push({
|
links.push({
|
||||||
|
|
|
||||||
|
|
@ -506,6 +506,7 @@
|
||||||
"com_sidepanel_hide_panel": "Hide Panel",
|
"com_sidepanel_hide_panel": "Hide Panel",
|
||||||
"com_sidepanel_manage_files": "Manage Files",
|
"com_sidepanel_manage_files": "Manage Files",
|
||||||
"com_sidepanel_mcp_no_servers_with_vars": "No MCP servers with configurable variables.",
|
"com_sidepanel_mcp_no_servers_with_vars": "No MCP servers with configurable variables.",
|
||||||
|
"com_sidepanel_mcp_no_custom_vars": "No custom user variables set for {{0}}",
|
||||||
"com_sidepanel_mcp_variables_for": "MCP Variables for {{0}}",
|
"com_sidepanel_mcp_variables_for": "MCP Variables for {{0}}",
|
||||||
"com_sidepanel_parameters": "Parameters",
|
"com_sidepanel_parameters": "Parameters",
|
||||||
"com_sources_image_alt": "Search result image",
|
"com_sources_image_alt": "Search result image",
|
||||||
|
|
@ -851,7 +852,6 @@
|
||||||
"com_ui_mcp_authenticated_success": "MCP server '{{0}}' authenticated successfully",
|
"com_ui_mcp_authenticated_success": "MCP server '{{0}}' authenticated successfully",
|
||||||
"com_ui_mcp_dialog_desc": "Please enter the necessary information below.",
|
"com_ui_mcp_dialog_desc": "Please enter the necessary information below.",
|
||||||
"com_ui_mcp_enter_var": "Enter value for {{0}}",
|
"com_ui_mcp_enter_var": "Enter value for {{0}}",
|
||||||
"com_ui_mcp_init_cancelled": "MCP server '{{0}}' initialization was cancelled due to simultaneous request",
|
|
||||||
"com_ui_mcp_init_failed": "Failed to initialize MCP server",
|
"com_ui_mcp_init_failed": "Failed to initialize MCP server",
|
||||||
"com_ui_mcp_initialize": "Initialize",
|
"com_ui_mcp_initialize": "Initialize",
|
||||||
"com_ui_mcp_initialized_success": "MCP server '{{0}}' initialized successfully",
|
"com_ui_mcp_initialized_success": "MCP server '{{0}}' initialized successfully",
|
||||||
|
|
|
||||||
|
|
@ -613,6 +613,8 @@ export type TStartupConfig = {
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
chatMenu?: boolean;
|
chatMenu?: boolean;
|
||||||
|
isOAuth?: boolean;
|
||||||
|
startup?: boolean;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
mcpPlaceholder?: string;
|
mcpPlaceholder?: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue