From c47e5b706bc7109e6915cdadee2100101cc19321 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 27 Jul 2026 09:17:49 -0400 Subject: [PATCH] fix: thread configured server names through display parsing parseToolName and getMCPServerName resolved context-free, so a configured server whose name contains the delimiter showed the wrong server in grouped tool summaries and subagent tool labels, and stacked icons missed its entry in the icon map. Both take the configured names now, supplied by the components that render them. Adds the hook to SubagentCall's mock factory: the spec renders the real component, so an unmocked useMCPServerNames would reach the query with no provider. --- .../Chat/Messages/Content/Parts/SubagentCall.tsx | 14 +++++++++++--- .../Content/Parts/__tests__/SubagentCall.test.tsx | 5 +++++ .../Chat/Messages/Content/ToolCallGroup.tsx | 7 ++++--- .../Content/ToolOutput/StackedToolIcons.tsx | 8 +++++--- .../Chat/Messages/Content/ToolOutput/ToolIcon.tsx | 4 ++-- client/src/utils/toolLabels.ts | 10 +++++++--- 6 files changed, 34 insertions(+), 14 deletions(-) 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;