diff --git a/webview-ui/src/components/chat/McpExecution.tsx b/webview-ui/src/components/chat/McpExecution.tsx index 9e48552fdc8..c1b0dc6871b 100644 --- a/webview-ui/src/components/chat/McpExecution.tsx +++ b/webview-ui/src/components/chat/McpExecution.tsx @@ -14,12 +14,35 @@ import { safeJsonParse } from "@roo/core" import { cn } from "@src/lib/utils" import { Button } from "@src/components/ui" +import { useExtensionState } from "@src/context/ExtensionStateContext" import CodeBlock from "../common/CodeBlock" import McpToolRow from "../mcp/McpToolRow" import { Markdown } from "./Markdown" +/** + * Truncates workspace paths in a JSON string by replacing the workspace root with "./" + * @param jsonString - The JSON string containing paths to truncate + * @param cwd - The current workspace directory (optional) + * @returns The JSON string with truncated paths + */ +function truncateWorkspacePaths(jsonString: string, cwd?: string): string { + if (!jsonString || !cwd) return jsonString + + // Normalize the cwd to handle different path separators + const normalizedCwd = cwd.replace(/\\/g, "/") + + // Escape special regex characters in the path + const escapedCwd = normalizedCwd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + + // Create a regex that matches the workspace path (with optional trailing slash) + const workspacePathRegex = new RegExp(escapedCwd + "/?", "g") + + // Replace workspace paths with "./" + return jsonString.replace(workspacePathRegex, "./") +} + interface McpExecutionProps { executionId: string text?: string @@ -49,6 +72,7 @@ export const McpExecution = ({ alwaysAllowMcp = false, }: McpExecutionProps) => { const { t } = useTranslation("mcp") + const { cwd } = useExtensionState() // State for tracking MCP response status const [status, setStatus] = useState(null) @@ -92,6 +116,7 @@ export const McpExecution = ({ }, [responseText, isResponseExpanded, tryParseJson, status]) // Only parse arguments data when complete to avoid parsing partial JSON + // Also apply workspace path truncation for cleaner display const argumentsData = useMemo(() => { if (!argumentsText) { return { isJson: false, formatted: "" } @@ -108,19 +133,22 @@ export const McpExecution = ({ // Try to parse, but if it fails, return as-is try { const parsed = JSON.parse(trimmed) + const formatted = JSON.stringify(parsed, null, 2) + // Apply path truncation to show cleaner paths + const truncated = truncateWorkspacePaths(formatted, cwd) return { isJson: true, - formatted: JSON.stringify(parsed, null, 2), + formatted: truncated, } } catch { - // JSON structure looks complete but is invalid, return as-is - return { isJson: false, formatted: argumentsText } + // JSON structure looks complete but is invalid, return as-is (but still truncate paths) + return { isJson: false, formatted: truncateWorkspacePaths(argumentsText, cwd) } } } - // For non-JSON or incomplete data, just return as-is - return { isJson: false, formatted: argumentsText } - }, [argumentsText]) + // For non-JSON or incomplete data, just return as-is (but still truncate paths) + return { isJson: false, formatted: truncateWorkspacePaths(argumentsText, cwd) } + }, [argumentsText, cwd]) const formattedResponseText = responseData.formatted const formattedArgumentsText = argumentsData.formatted diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index 5dea579193f..1745867ba9f 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -1,9 +1,12 @@ +import { useState } from "react" import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" +import { ChevronDown } from "lucide-react" import type { McpTool } from "@roo-code/types" import { useAppTranslation } from "@src/i18n/TranslationContext" import { vscode } from "@src/utils/vscode" +import { cn } from "@src/lib/utils" import { StandardTooltip, ToggleSwitch } from "@/components/ui" type McpToolRowProps = { @@ -17,6 +20,7 @@ type McpToolRowProps = { const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp, isInChatContext = false }: McpToolRowProps) => { const { t } = useAppTranslation() const isToolEnabled = tool.enabledForPrompt ?? true + const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) const handleAlwaysAllowChange = () => { if (!serverName) return @@ -99,10 +103,39 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp, isInChatCo {tool.description && (
- {tool.description} + className={cn("mt-1 text-xs text-vscode-descriptionForeground", { + "opacity-80": isToolEnabled, + "opacity-40": !isToolEnabled, + })}> + {isInChatContext ? ( +
+ + + + + setIsDescriptionExpanded(!isDescriptionExpanded)}> + {tool.description} + + +
+ ) : ( + tool.description + )}
)} {isToolEnabled && diff --git a/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx b/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx index f686a23f001..e7218932ac1 100644 --- a/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx +++ b/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx @@ -13,6 +13,8 @@ vi.mock("@src/i18n/TranslationContext", () => ({ "mcp:tool.parameters": "Parameters", "mcp:tool.noDescription": "No description", "mcp:tool.togglePromptInclusion": "Toggle prompt inclusion", + "mcp:tool.expand": "Expand description", + "mcp:tool.collapse": "Collapse description", } return translations[key] || key }, @@ -285,4 +287,72 @@ describe("McpToolRow", () => { expect(toolDescription).toHaveClass("opacity-80") expect(toolDescription).not.toHaveClass("opacity-40") }) + + describe("collapsible description in chat context", () => { + const toolWithLongDescription = { + ...mockTool, + description: + "This is a very long description that should be truncated to two lines when displayed in chat context. It provides detailed information about what the tool does and how it works.", + } + + it("shows collapsible description when isInChatContext is true", () => { + render() + + // Should have the toggle button + const toggleButton = screen.getByTestId("description-toggle") + expect(toggleButton).toBeInTheDocument() + }) + + it("does not show collapsible description when isInChatContext is false", () => { + render() + + // Should not have the toggle button + const toggleButton = screen.queryByTestId("description-toggle") + expect(toggleButton).not.toBeInTheDocument() + }) + + it("expands description when toggle button is clicked", () => { + render() + + const toggleButton = screen.getByTestId("description-toggle") + + // Initially collapsed - description should have line-clamp-2 class + const descriptionText = screen.getByText(toolWithLongDescription.description) + expect(descriptionText).toHaveClass("line-clamp-2") + + // Click to expand + fireEvent.click(toggleButton) + + // After expanding - description should not have line-clamp-2 class + expect(descriptionText).not.toHaveClass("line-clamp-2") + }) + + it("collapses description when toggle button is clicked twice", () => { + render() + + const toggleButton = screen.getByTestId("description-toggle") + const descriptionText = screen.getByText(toolWithLongDescription.description) + + // Click to expand + fireEvent.click(toggleButton) + expect(descriptionText).not.toHaveClass("line-clamp-2") + + // Click again to collapse + fireEvent.click(toggleButton) + expect(descriptionText).toHaveClass("line-clamp-2") + }) + + it("expands description when clicking on the description text", () => { + render() + + const descriptionText = screen.getByText(toolWithLongDescription.description) + expect(descriptionText).toHaveClass("line-clamp-2") + + // Click on the description text + fireEvent.click(descriptionText) + + // Should expand + expect(descriptionText).not.toHaveClass("line-clamp-2") + }) + }) }) diff --git a/webview-ui/src/i18n/locales/ca/mcp.json b/webview-ui/src/i18n/locales/ca/mcp.json index 654467b4435..17fbbc13f15 100644 --- a/webview-ui/src/i18n/locales/ca/mcp.json +++ b/webview-ui/src/i18n/locales/ca/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Permet sempre", "parameters": "Paràmetres", "noDescription": "Sense descripció", - "togglePromptInclusion": "Canviar inclusió al prompt" + "togglePromptInclusion": "Canviar inclusió al prompt", + "expand": "Ampliar descripció", + "collapse": "Reduir descripció" }, "tabs": { "tools": "Eines", diff --git a/webview-ui/src/i18n/locales/de/mcp.json b/webview-ui/src/i18n/locales/de/mcp.json index 50d27395a6c..967324947ff 100644 --- a/webview-ui/src/i18n/locales/de/mcp.json +++ b/webview-ui/src/i18n/locales/de/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Immer erlauben", "parameters": "Parameter", "noDescription": "Keine Beschreibung", - "togglePromptInclusion": "Einbeziehung in Prompt umschalten" + "togglePromptInclusion": "Einbeziehung in Prompt umschalten", + "expand": "Beschreibung ausklappen", + "collapse": "Beschreibung einklappen" }, "tabs": { "tools": "Tools", diff --git a/webview-ui/src/i18n/locales/en/mcp.json b/webview-ui/src/i18n/locales/en/mcp.json index 2fcae2840c7..b5e01cec335 100644 --- a/webview-ui/src/i18n/locales/en/mcp.json +++ b/webview-ui/src/i18n/locales/en/mcp.json @@ -21,7 +21,9 @@ "alwaysAllow": "Always allow", "parameters": "Parameters", "noDescription": "No description", - "togglePromptInclusion": "Toggle inclusion in prompt" + "togglePromptInclusion": "Toggle inclusion in prompt", + "expand": "Expand description", + "collapse": "Collapse description" }, "tabs": { "tools": "Tools", diff --git a/webview-ui/src/i18n/locales/es/mcp.json b/webview-ui/src/i18n/locales/es/mcp.json index 5b7d8326267..44640172d06 100644 --- a/webview-ui/src/i18n/locales/es/mcp.json +++ b/webview-ui/src/i18n/locales/es/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Permitir siempre", "parameters": "Parámetros", "noDescription": "Sin descripción", - "togglePromptInclusion": "Alternar inclusión en el prompt" + "togglePromptInclusion": "Alternar inclusión en el prompt", + "expand": "Expandir descripción", + "collapse": "Contraer descripción" }, "tabs": { "tools": "Herramientas", diff --git a/webview-ui/src/i18n/locales/fr/mcp.json b/webview-ui/src/i18n/locales/fr/mcp.json index 33fa3937968..410dc1c0a0c 100644 --- a/webview-ui/src/i18n/locales/fr/mcp.json +++ b/webview-ui/src/i18n/locales/fr/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Toujours autoriser", "parameters": "Paramètres", "noDescription": "Aucune description", - "togglePromptInclusion": "Basculer l'inclusion dans le prompt" + "togglePromptInclusion": "Basculer l'inclusion dans le prompt", + "expand": "Développer la description", + "collapse": "Réduire la description" }, "tabs": { "tools": "Outils", diff --git a/webview-ui/src/i18n/locales/hi/mcp.json b/webview-ui/src/i18n/locales/hi/mcp.json index 6a7bdd679ab..a5cea25c4f7 100644 --- a/webview-ui/src/i18n/locales/hi/mcp.json +++ b/webview-ui/src/i18n/locales/hi/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "हमेशा अनुमति दें", "parameters": "पैरामीटर", "noDescription": "कोई विवरण नहीं", - "togglePromptInclusion": "प्रॉम्प्ट में शामिल करना टॉगल करें" + "togglePromptInclusion": "प्रॉम्प्ट में शामिल करना टॉगल करें", + "expand": "विवरण विस्तारित करें", + "collapse": "विवरण संक्षिप्त करें" }, "tabs": { "tools": "टूल्स", diff --git a/webview-ui/src/i18n/locales/id/mcp.json b/webview-ui/src/i18n/locales/id/mcp.json index 6ae62ffd5ec..cec117e2118 100644 --- a/webview-ui/src/i18n/locales/id/mcp.json +++ b/webview-ui/src/i18n/locales/id/mcp.json @@ -21,7 +21,9 @@ "alwaysAllow": "Selalu izinkan", "parameters": "Parameter", "noDescription": "Tidak ada deskripsi", - "togglePromptInclusion": "Aktifkan daya untuk meminta" + "togglePromptInclusion": "Aktifkan daya untuk meminta", + "expand": "Perluas deskripsi", + "collapse": "Ciutkan deskripsi" }, "tabs": { "tools": "Tools", diff --git a/webview-ui/src/i18n/locales/it/mcp.json b/webview-ui/src/i18n/locales/it/mcp.json index 3b6dce9cb15..ce90f9c68b0 100644 --- a/webview-ui/src/i18n/locales/it/mcp.json +++ b/webview-ui/src/i18n/locales/it/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Consenti sempre", "parameters": "Parametri", "noDescription": "Nessuna descrizione", - "togglePromptInclusion": "Attiva/disattiva inclusione nel prompt" + "togglePromptInclusion": "Attiva/disattiva inclusione nel prompt", + "expand": "Espandi descrizione", + "collapse": "Comprimi descrizione" }, "tabs": { "tools": "Strumenti", diff --git a/webview-ui/src/i18n/locales/ja/mcp.json b/webview-ui/src/i18n/locales/ja/mcp.json index d415e30f08f..4cfad23aabf 100644 --- a/webview-ui/src/i18n/locales/ja/mcp.json +++ b/webview-ui/src/i18n/locales/ja/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "常に許可", "parameters": "パラメータ", "noDescription": "説明なし", - "togglePromptInclusion": "プロンプトへの含有を切り替える" + "togglePromptInclusion": "プロンプトへの含有を切り替える", + "expand": "説明を展開", + "collapse": "説明を折りたたむ" }, "tabs": { "tools": "ツール", diff --git a/webview-ui/src/i18n/locales/ko/mcp.json b/webview-ui/src/i18n/locales/ko/mcp.json index b0ecb215e1c..c3df4738ba4 100644 --- a/webview-ui/src/i18n/locales/ko/mcp.json +++ b/webview-ui/src/i18n/locales/ko/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "항상 허용", "parameters": "매개변수", "noDescription": "설명 없음", - "togglePromptInclusion": "프롬프트 포함 여부 전환" + "togglePromptInclusion": "프롬프트 포함 여부 전환", + "expand": "설명 펼치기", + "collapse": "설명 접기" }, "tabs": { "tools": "도구", diff --git a/webview-ui/src/i18n/locales/nl/mcp.json b/webview-ui/src/i18n/locales/nl/mcp.json index 1eceb8681f0..103595349b2 100644 --- a/webview-ui/src/i18n/locales/nl/mcp.json +++ b/webview-ui/src/i18n/locales/nl/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Altijd toestaan", "parameters": "Parameters", "noDescription": "Geen beschrijving", - "togglePromptInclusion": "Inclusie in prompt in-/uitschakelen" + "togglePromptInclusion": "Inclusie in prompt in-/uitschakelen", + "expand": "Beschrijving uitvouwen", + "collapse": "Beschrijving invouwen" }, "tabs": { "tools": "Tools", diff --git a/webview-ui/src/i18n/locales/pl/mcp.json b/webview-ui/src/i18n/locales/pl/mcp.json index 100e8748c88..e1a376ad834 100644 --- a/webview-ui/src/i18n/locales/pl/mcp.json +++ b/webview-ui/src/i18n/locales/pl/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Zawsze pozwalaj", "parameters": "Parametry", "noDescription": "Brak opisu", - "togglePromptInclusion": "Przełącz uwzględnianie w podpowiedzi" + "togglePromptInclusion": "Przełącz uwzględnianie w podpowiedzi", + "expand": "Rozwiń opis", + "collapse": "Zwiń opis" }, "tabs": { "tools": "Narzędzia", diff --git a/webview-ui/src/i18n/locales/pt-BR/mcp.json b/webview-ui/src/i18n/locales/pt-BR/mcp.json index da85d1ef6eb..f4500333fe8 100644 --- a/webview-ui/src/i18n/locales/pt-BR/mcp.json +++ b/webview-ui/src/i18n/locales/pt-BR/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Sempre permitir", "parameters": "Parâmetros", "noDescription": "Sem descrição", - "togglePromptInclusion": "Alternar inclusão no prompt" + "togglePromptInclusion": "Alternar inclusão no prompt", + "expand": "Expandir descrição", + "collapse": "Recolher descrição" }, "tabs": { "tools": "Ferramentas", diff --git a/webview-ui/src/i18n/locales/ru/mcp.json b/webview-ui/src/i18n/locales/ru/mcp.json index a2be25f5d6d..4c22fdd1d51 100644 --- a/webview-ui/src/i18n/locales/ru/mcp.json +++ b/webview-ui/src/i18n/locales/ru/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Всегда разрешать", "parameters": "Параметры", "noDescription": "Нет описания", - "togglePromptInclusion": "Переключить включение в промпт" + "togglePromptInclusion": "Переключить включение в промпт", + "expand": "Развернуть описание", + "collapse": "Свернуть описание" }, "tabs": { "tools": "Инструменты", diff --git a/webview-ui/src/i18n/locales/tr/mcp.json b/webview-ui/src/i18n/locales/tr/mcp.json index 18bc2af5f30..12949085f28 100644 --- a/webview-ui/src/i18n/locales/tr/mcp.json +++ b/webview-ui/src/i18n/locales/tr/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Her zaman izin ver", "parameters": "Parametreler", "noDescription": "Açıklama yok", - "togglePromptInclusion": "Komut isteminde dahil etmeyi aç/kapat" + "togglePromptInclusion": "Komut isteminde dahil etmeyi aç/kapat", + "expand": "Açıklamayı genişlet", + "collapse": "Açıklamayı daralt" }, "tabs": { "tools": "Araçlar", diff --git a/webview-ui/src/i18n/locales/vi/mcp.json b/webview-ui/src/i18n/locales/vi/mcp.json index d7d9a29d740..cf7e4f3fdb4 100644 --- a/webview-ui/src/i18n/locales/vi/mcp.json +++ b/webview-ui/src/i18n/locales/vi/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "Luôn cho phép", "parameters": "Tham số", "noDescription": "Không có mô tả", - "togglePromptInclusion": "Bật/tắt bao gồm trong lời nhắc" + "togglePromptInclusion": "Bật/tắt bao gồm trong lời nhắc", + "expand": "Mở rộng mô tả", + "collapse": "Thu gọn mô tả" }, "tabs": { "tools": "Công cụ", diff --git a/webview-ui/src/i18n/locales/zh-CN/mcp.json b/webview-ui/src/i18n/locales/zh-CN/mcp.json index 601d1b2a135..d6f9c8d7573 100644 --- a/webview-ui/src/i18n/locales/zh-CN/mcp.json +++ b/webview-ui/src/i18n/locales/zh-CN/mcp.json @@ -20,7 +20,9 @@ "alwaysAllow": "始终允许", "parameters": "参数", "noDescription": "无描述", - "togglePromptInclusion": "切换在提示中的包含" + "togglePromptInclusion": "切换在提示中的包含", + "expand": "展开描述", + "collapse": "收起描述" }, "tabs": { "tools": "工具", diff --git a/webview-ui/src/i18n/locales/zh-TW/mcp.json b/webview-ui/src/i18n/locales/zh-TW/mcp.json index ab01f9f2cfd..b5b985b1e73 100644 --- a/webview-ui/src/i18n/locales/zh-TW/mcp.json +++ b/webview-ui/src/i18n/locales/zh-TW/mcp.json @@ -21,7 +21,9 @@ "alwaysAllow": "總是允許", "parameters": "參數", "noDescription": "暫無說明", - "togglePromptInclusion": "切換提示包含狀態" + "togglePromptInclusion": "切換提示包含狀態", + "expand": "展開說明", + "collapse": "收合說明" }, "tabs": { "tools": "工具",