🧰 feat: Accessible MCP Tool Lists (#10695)

* feat: add aria-label for expansion chevron in Agent Builder's MCP tool list dropdown

* fix: remove duplicate tool info button in MCPTool so it doesn't get picked up via keyboard nav (still exists on mouse hover as it should to provide tooltip description of tool)

* feat: use InfoHoverCard rather than Ariakit components for tool descriptions

* chore: remove unused i18n keys
This commit is contained in:
Dustin Healy 2025-11-27 09:22:29 -08:00 committed by Danny Avila
parent b1e31fdc97
commit 58f73626e7
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
2 changed files with 20 additions and 57 deletions

View file

@ -1,17 +1,17 @@
import React, { useState } from 'react';
import * as Ariakit from '@ariakit/react';
import { ChevronDown } from 'lucide-react';
import { useFormContext } from 'react-hook-form';
import { Constants } from 'librechat-data-provider';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import {
Label,
ESide,
Checkbox,
OGDialog,
Accordion,
TrashIcon,
InfoHoverCard,
AccordionItem,
CircleHelpIcon,
OGDialogTrigger,
AccordionContent,
OGDialogTemplate,
@ -31,7 +31,6 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
const [isFocused, setIsFocused] = useState(false);
const [isHovering, setIsHovering] = useState(false);
const [accordionValue, setAccordionValue] = useState<string>('');
const [hoveredToolId, setHoveredToolId] = useState<string | null>(null);
if (!serverInfo) {
return null;
@ -183,7 +182,16 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
'flex h-7 w-7 items-center justify-center rounded transition-colors duration-200 hover:bg-surface-active-alt focus:translate-x-0 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
isExpanded && 'bg-surface-active-alt',
)}
aria-hidden="true"
aria-label={
isExpanded
? localize('com_ui_tool_list_collapse', {
serverName: currentServerName,
})
: localize('com_ui_tool_list_expand', {
serverName: currentServerName,
})
}
aria-expanded={isExpanded}
tabIndex={0}
onFocus={() => setIsFocused(true)}
>
@ -227,15 +235,13 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
key={subTool.tool_id}
htmlFor={subTool.tool_id}
className={cn(
'border-token-border-light hover:bg-token-surface-secondary flex cursor-pointer items-center rounded-lg border p-2',
'group/item border-token-border-light hover:bg-token-surface-secondary flex cursor-pointer items-center rounded-lg border p-2',
'ml-2 mr-1 focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background',
)}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
e.stopPropagation();
}}
onMouseEnter={() => setHoveredToolId(subTool.tool_id)}
onMouseLeave={() => setHoveredToolId(null)}
>
<Checkbox
id={subTool.tool_id}
@ -264,54 +270,9 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
{subTool.metadata.name}
</span>
{subTool.metadata.description && (
<Ariakit.HovercardProvider placement="left-start">
<div className="ml-auto flex h-6 w-6 items-center justify-center">
<Ariakit.HovercardAnchor
render={
<Ariakit.Button
className={cn(
'flex h-5 w-5 cursor-help items-center rounded-full text-text-secondary transition-opacity duration-200',
hoveredToolId === subTool.tool_id ? 'opacity-100' : 'opacity-0',
)}
aria-label={localize('com_ui_tool_info')}
>
<CircleHelpIcon className="h-4 w-4" />
<Ariakit.VisuallyHidden>
{localize('com_ui_tool_info')}
</Ariakit.VisuallyHidden>
</Ariakit.Button>
}
/>
<Ariakit.HovercardDisclosure
className="rounded-full text-text-secondary focus:outline-none focus:ring-2 focus:ring-ring"
aria-label={localize('com_ui_tool_more_info')}
aria-expanded={hoveredToolId === subTool.tool_id}
aria-controls={`tool-description-${subTool.tool_id}`}
>
<Ariakit.VisuallyHidden>
{localize('com_ui_tool_more_info')}
</Ariakit.VisuallyHidden>
<ChevronDown className="h-4 w-4" aria-hidden="true" />
</Ariakit.HovercardDisclosure>
</div>
<Ariakit.Hovercard
id={`tool-description-${subTool.tool_id}`}
gutter={14}
shift={40}
flip={false}
className="z-[999] w-80 scale-95 rounded-2xl border border-border-medium bg-surface-secondary p-4 text-text-primary opacity-0 shadow-md transition-all duration-200 data-[enter]:scale-100 data-[leave]:scale-95 data-[enter]:opacity-100 data-[leave]:opacity-0"
portal={true}
unmountOnHide={true}
role="tooltip"
aria-label={subTool.metadata.description}
>
<div className="space-y-2">
<p className="text-sm text-text-secondary">
{subTool.metadata.description}
</p>
</div>
</Ariakit.Hovercard>
</Ariakit.HovercardProvider>
<div className="ml-auto flex items-center opacity-0 transition-opacity duration-200 group-focus-within/item:opacity-100 group-hover/item:opacity-100">
<InfoHoverCard side={ESide.Left} text={subTool.metadata.description} />
</div>
)}
</label>
))}

View file

@ -782,6 +782,7 @@
"com_ui_close_settings": "Close Settings",
"com_ui_close_window": "Close Window",
"com_ui_code": "Code",
"com_ui_collapse": "Collapse",
"com_ui_collapse_chat": "Collapse Chat",
"com_ui_command_placeholder": "Optional: Enter a command for the prompt or name will be used",
"com_ui_command_usage_placeholder": "Select a Prompt by command or name",
@ -918,6 +919,7 @@
"com_ui_error_updating_preferences": "Error updating preferences",
"com_ui_everyone_permission_level": "Everyone's permission level",
"com_ui_examples": "Examples",
"com_ui_expand": "Expand",
"com_ui_expand_chat": "Expand Chat",
"com_ui_export_convo_modal": "Export Conversation Modal",
"com_ui_feedback_more": "More...",
@ -1282,8 +1284,8 @@
"com_ui_token_url": "Token URL",
"com_ui_tokens": "tokens",
"com_ui_tool_collection_prefix": "A collection of tools from",
"com_ui_tool_info": "Tool Information",
"com_ui_tool_more_info": "More information about this tool",
"com_ui_tool_list_collapse": "Collapse {{serverName}} tool list",
"com_ui_tool_list_expand": "Expand {{serverName}} tool list",
"com_ui_tools": "Tools",
"com_ui_tools_and_actions": "Tools and Actions",
"com_ui_transferred_to": "Transferred to",