diff --git a/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx b/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx index 3c58576103..879b48d340 100644 --- a/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/SubagentCall.tsx @@ -16,6 +16,7 @@ import { MessageContext } from '~/Providers/MessageContext'; import MessageIcon from '~/components/Share/MessageIcon'; import { subagentProgressByToolCallId } from '~/store'; import { useAgentsMapContext } from '~/Providers'; +import { useMCPServerNames } from '~/hooks/MCP'; import { AttachmentGroup } from './Attachment'; import { useLocalize } from '~/hooks'; import Reasoning from './Reasoning'; @@ -704,11 +705,13 @@ function ToolNameBadge({ name }: { name: string }): JSX.Element { function ToolIdentifier({ rawName, localize, + mcpServerNames, }: { rawName: string; localize: ReturnType; + mcpServerNames?: readonly string[]; }): JSX.Element { - const parsed = parseToolName(rawName); + const parsed = parseToolName(rawName, mcpServerNames); if (parsed.mcpServer) { return ( @@ -740,6 +743,7 @@ function ToolIdentifier({ */ function TickerLineView({ line }: { line: SubagentTickerLine }): JSX.Element { const localize = useLocalize(); + const mcpServerNames = useMCPServerNames(); if (line.kind === 'writing' || line.kind === 'reasoning') { const prefix = line.kind === 'writing' @@ -766,7 +770,7 @@ function TickerLineView({ line }: { line: SubagentTickerLine }): JSX.Element { {line.toolNames.map((name, i) => ( {i > 0 && ,} - + ))} {line.argsSnippet && ( @@ -779,7 +783,11 @@ function TickerLineView({ line }: { line: SubagentTickerLine }): JSX.Element { if (line.kind === 'tool_complete') { return (
  • - + ({ ), })); +jest.mock('~/hooks/MCP', () => { + const mcpServerNames: string[] = []; + return { useMCPServerNames: () => mcpServerNames }; +}); + jest.mock('~/utils', () => ({ ...jest.requireActual('~/utils/groupToolCalls'), ...jest.requireActual('~/utils/toolLabels'), diff --git a/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx b/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx index 1d86ac356d..5c386cc06d 100644 --- a/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx +++ b/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx @@ -10,11 +10,11 @@ import type { } from 'librechat-data-provider'; import type { PartWithIndex } from './ParallelContent'; import { useLocalize, useExpandCollapse, scheduleMessageContentLayoutReconcile } from '~/hooks'; +import { useMCPIconMap, useMCPServerNames } from '~/hooks/MCP'; import { isBashProgrammaticToolCall } from './routing'; import { ASK_USER_QUESTION } from '~/utils/approval'; import { cn, getToolDisplayLabel } from '~/utils'; import { StackedToolIcons } from './ToolOutput'; -import { useMCPIconMap } from '~/hooks/MCP'; import { AttachmentGroup } from './Parts'; import store from '~/store'; @@ -126,6 +126,7 @@ export default function ToolCallGroup({ }: ToolCallGroupProps) { const localize = useLocalize(); const mcpIconMap = useMCPIconMap(); + const mcpServerNames = useMCPServerNames(); const rootRef = useRef(null); const cancelLayoutReconcileRef = useRef<(() => void) | null>(null); const retainedForPendingApprovalRef = useRef(false); @@ -179,7 +180,7 @@ export default function ToolCallGroup({ const labels: string[] = []; for (const rawName of toolNames) { if (!rawName) continue; - const label = getToolDisplayLabel(rawName, localize); + const label = getToolDisplayLabel(rawName, localize, mcpServerNames); if (!seen.has(label)) { seen.add(label); labels.push(label); @@ -189,7 +190,7 @@ export default function ToolCallGroup({ return labels.join(', '); } return `${labels.slice(0, 3).join(', ')}, +${labels.length - 3}`; - }, [toolNames, localize]); + }, [toolNames, localize, mcpServerNames]); const autoExpand = useRecoilValue(store.autoExpandTools); const autoCollapse = !autoExpand && count >= 2 && allCompleted; diff --git a/client/src/components/Chat/Messages/Content/ToolOutput/StackedToolIcons.tsx b/client/src/components/Chat/Messages/Content/ToolOutput/StackedToolIcons.tsx index c7be3adc25..6ffd0d4c9d 100644 --- a/client/src/components/Chat/Messages/Content/ToolOutput/StackedToolIcons.tsx +++ b/client/src/components/Chat/Messages/Content/ToolOutput/StackedToolIcons.tsx @@ -1,6 +1,7 @@ import { useMemo } from 'react'; -import ToolIcon, { getToolIconType, getMCPServerName } from './ToolIcon'; import type { ToolIconType } from './ToolIcon'; +import ToolIcon, { getToolIconType, getMCPServerName } from './ToolIcon'; +import { useMCPServerNames } from '~/hooks/MCP'; import { cn } from '~/utils'; interface ResolvedIcon { @@ -22,12 +23,13 @@ export default function StackedToolIcons({ maxIcons = 3, isAnimating = false, }: StackedToolIconsProps) { + const mcpServerNames = useMCPServerNames(); const uniqueIcons = useMemo(() => { const seen = new Set(); const result: ResolvedIcon[] = []; for (const name of toolNames) { const type = getToolIconType(name); - const serverName = getMCPServerName(name); + const serverName = getMCPServerName(name, mcpServerNames); const iconUrl = serverName ? mcpIconMap?.get(serverName) : undefined; const key = iconUrl ? `mcp-${serverName}` : type; if (!seen.has(key)) { @@ -36,7 +38,7 @@ export default function StackedToolIcons({ } } return result; - }, [toolNames, mcpIconMap]); + }, [toolNames, mcpIconMap, mcpServerNames]); const visibleIcons = uniqueIcons.slice(0, maxIcons); const overflowCount = uniqueIcons.length - visibleIcons.length; diff --git a/client/src/components/Chat/Messages/Content/ToolOutput/ToolIcon.tsx b/client/src/components/Chat/Messages/Content/ToolOutput/ToolIcon.tsx index 1e9a7b0d1e..f90dddc493 100644 --- a/client/src/components/Chat/Messages/Content/ToolOutput/ToolIcon.tsx +++ b/client/src/components/Chat/Messages/Content/ToolOutput/ToolIcon.tsx @@ -91,11 +91,11 @@ export function getToolIconType(name: string): ToolIconType { } /** Extracts the MCP server name from a tool name with format `toolserver`. */ -export function getMCPServerName(toolName: string): string { +export function getMCPServerName(toolName: string, knownServerNames?: readonly string[]): string { if (!toolName.includes(Constants.mcp_delimiter)) { return ''; } - const [, serverName] = splitToolCallName(toolName); + const [, serverName] = splitToolCallName(toolName, knownServerNames); return serverName ?? ''; } diff --git a/client/src/utils/toolLabels.ts b/client/src/utils/toolLabels.ts index 4b5fda8913..58757b9ce8 100644 --- a/client/src/utils/toolLabels.ts +++ b/client/src/utils/toolLabels.ts @@ -46,9 +46,12 @@ export interface ParsedToolName { * - `web_search` → `{ mcpServer: '', toolName: 'web_search', friendlyKey: 'com_ui_tool_name_web_search' }` * - `some_custom_tool` → `{ mcpServer: '', toolName: 'some_custom_tool' }` */ -export function parseToolName(rawName: string): ParsedToolName { +export function parseToolName( + rawName: string, + knownServerNames?: readonly string[], +): ParsedToolName { if (rawName.includes(Constants.mcp_delimiter)) { - const [toolName, mcpServer = ''] = splitToolCallName(rawName); + const [toolName, mcpServer = ''] = splitToolCallName(rawName, knownServerNames); return { raw: rawName, mcpServer, toolName }; } const friendlyKey = TOOL_FRIENDLY_NAME_KEYS[rawName]; @@ -72,8 +75,9 @@ export function parseToolName(rawName: string): ParsedToolName { export function getToolDisplayLabel( rawName: string, localize: (key: TranslationKeys) => string, + knownServerNames?: readonly string[], ): string { - const parsed = parseToolName(rawName); + const parsed = parseToolName(rawName, knownServerNames); if (parsed.mcpServer) return parsed.mcpServer; if (parsed.friendlyKey) return localize(parsed.friendlyKey); return parsed.toolName;