🔍 feat: Add Filter to MCP Builder Panel (#10885)

Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
This commit is contained in:
Atef Bellaaj 2025-12-10 16:36:41 +01:00 committed by Danny Avila
parent 6fc6471010
commit da9b5196aa
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
3 changed files with 49 additions and 7 deletions

View file

@ -1,7 +1,7 @@
import { useState, useRef } from 'react';
import { Plus } from 'lucide-react';
import { useState, useRef, useMemo } from 'react';
import { Plus, Search } from 'lucide-react';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import { Button, Spinner, OGDialogTrigger } from '@librechat/client';
import { Button, Spinner, OGDialogTrigger, Input } from '@librechat/client';
import { useLocalize, useMCPServerManager, useHasAccess } from '~/hooks';
import MCPServerList from './MCPServerList';
import MCPServerDialog from './MCPServerDialog';
@ -18,15 +18,41 @@ export default function MCPBuilderPanel() {
permission: Permissions.CREATE,
});
const [showDialog, setShowDialog] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const addButtonRef = useRef<HTMLButtonElement | null>(null);
const configDialogProps = getConfigDialogProps();
const filteredServers = useMemo(() => {
if (!searchQuery.trim()) {
return availableMCPServers;
}
const query = searchQuery.toLowerCase();
return availableMCPServers.filter((server) => {
const displayName = server.config?.title || server.serverName;
return (
displayName.toLowerCase().includes(query) || server.serverName.toLowerCase().includes(query)
);
});
}, [availableMCPServers, searchQuery]);
return (
<div className="flex h-full w-full flex-col overflow-visible">
<div role="region" aria-label="MCP Builder" className="mt-2 space-y-2">
{/* Admin Settings Button */}
<MCPAdminSettings />
{/* Search Input */}
<div className="relative">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" />
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={localize('com_ui_filter_mcp_servers')}
className="pl-9"
aria-label={localize('com_ui_filter_mcp_servers')}
/>
</div>
{hasCreateAccess && (
<MCPServerDialog open={showDialog} onOpenChange={setShowDialog} triggerRef={addButtonRef}>
<OGDialogTrigger asChild>
@ -52,8 +78,9 @@ export default function MCPBuilderPanel() {
</div>
) : (
<MCPServerList
servers={availableMCPServers}
servers={filteredServers}
getServerStatusIconProps={getServerStatusIconProps}
isFiltered={searchQuery.trim().length > 0}
/>
)}
{configDialogProps && <MCPConfigDialog {...configDialogProps} />}

View file

@ -15,6 +15,7 @@ interface MCPServerListProps {
getServerStatusIconProps: (
serverName: string,
) => React.ComponentProps<typeof MCPServerStatusIcon>;
isFiltered?: boolean;
}
// Self-contained edit button component (follows MemoryViewer pattern)
@ -39,7 +40,11 @@ const EditMCPServerButton = ({ server }: { server: MCPServerDefinition }) => {
);
};
export default function MCPServerList({ servers, getServerStatusIconProps }: MCPServerListProps) {
export default function MCPServerList({
servers,
getServerStatusIconProps,
isFiltered = false,
}: MCPServerListProps) {
const canCreateEditMCPs = useHasAccess({
permissionType: PermissionTypes.MCP_SERVERS,
permission: Permissions.CREATE,
@ -49,8 +54,16 @@ export default function MCPServerList({ servers, getServerStatusIconProps }: MCP
if (servers.length === 0) {
return (
<div className="rounded-lg border border-border-light bg-transparent p-8 text-center shadow-sm">
<p className="text-sm text-text-secondary">{localize('com_ui_no_mcp_servers')}</p>
<p className="mt-1 text-xs text-text-tertiary">{localize('com_ui_add_first_mcp_server')}</p>
{isFiltered ? (
<p className="text-sm text-text-secondary">{localize('com_ui_no_mcp_servers_match')}</p>
) : (
<>
<p className="text-sm text-text-secondary">{localize('com_ui_no_mcp_servers')}</p>
<p className="mt-1 text-xs text-text-tertiary">
{localize('com_ui_add_first_mcp_server')}
</p>
</>
)}
</div>
);
}