feat(ui,vscode): make MCP tools expandable like regular tools (#7168)

This commit is contained in:
Marius
2026-03-17 11:25:11 +01:00
committed by GitHub
parent 18c282d34c
commit 9f52ba9119
5 changed files with 120 additions and 2 deletions
@@ -78,5 +78,23 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
[data-slot="collapsible-trigger"][aria-expanded="true"] {
border-bottom: 1px solid var(--border-weak-base, var(--vscode-panel-border));
}
/* Arrow: DOWN when collapsed, UP when expanded (matches question-dock toggle) */
[data-slot="collapsible-arrow-icon"] {
transform: translateZ(0) rotate(0deg);
}
[data-slot="collapsible-trigger"][aria-expanded="true"] [data-slot="collapsible-arrow-icon"] {
transform: translateZ(0) rotate(180deg);
}
}
/* Expandable tool output content */
[data-component="tool-output"] {
padding: 8px 12px;
white-space: pre-wrap;
word-break: break-word;
min-width: 0;
overflow: hidden;
}
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:22c9bc250a347451c3a194f3ec701bfaf9db67f3104d12bde92adffc08bcd30b
size 6263
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2a1083201d8d567b16cad2a65890f4d24d9463b177d0cc09e20f5e5177ab5deb
size 21173
@@ -12,6 +12,7 @@ import type { AssistantMessage as SDKAssistantMessage, TextPart, ToolPart } from
import { StoryProviders, defaultMockData, mockSessionValue } from "./StoryProviders"
import { AssistantMessage } from "../components/chat/AssistantMessage"
import { ChatView } from "../components/chat/ChatView"
import { Part } from "@kilocode/kilo-ui/message-part"
import { registerVscodeToolOverrides } from "../components/chat/VscodeToolOverrides"
import { SessionContext } from "../context/session"
import type { PermissionRequest, QuestionRequest } from "../types/messages"
@@ -771,3 +772,73 @@ export const PermissionDockSubagent: Story = {
)
},
}
// ---------------------------------------------------------------------------
// 17. MCP tool cards — collapsed
// ---------------------------------------------------------------------------
const mcpCompleted: ToolPart = {
id: "part-mcp-001",
sessionID: SESSION_ID,
messageID: ASST_MSG_ID,
type: "tool",
callID: "call-mcp-001",
tool: "vercel_search_vercel_documentation",
state: {
status: "completed",
input: { topic: "serverless functions" },
output:
"## Serverless Function Configuration\n\nSource: https://vercel.com/docs/build-output-api/primitives\n\nThis TypeScript type definition (`ServerlessFunctionConfig`) specifies configuration for Vercel Serverless Functions.\n\n```ts\ntype ServerlessFunctionConfig = {\n handler: string;\n runtime: string;\n memory?: number;\n maxDuration?: number;\n}\n```",
title: "Search Vercel docs",
metadata: {},
time: { start: now - 4000, end: now - 3500 },
},
}
const mcpShort: ToolPart = {
id: "part-mcp-002",
sessionID: SESSION_ID,
messageID: ASST_MSG_ID,
type: "tool",
callID: "call-mcp-002",
tool: "sentry_search_issues",
state: {
status: "completed",
input: { query: "unresolved errors" },
output:
"Found 3 issues:\n- PROJ-123: TypeError in auth flow\n- PROJ-456: Network timeout\n- PROJ-789: Null reference",
title: "Search issues",
metadata: {},
time: { start: now - 3000, end: now - 2800 },
},
}
export const McpToolCards: Story = {
name: "MCP Tool Cards — collapsed",
render: () => {
const data = dataWith([mcpCompleted, mcpShort])
return (
<StoryProviders data={data} sessionID={SESSION_ID}>
<AssistantMessage message={baseAssistantMessage} />
</StoryProviders>
)
},
}
// ---------------------------------------------------------------------------
// 18. MCP tool card — expanded (defaultOpen)
// ---------------------------------------------------------------------------
export const McpToolExpanded: Story = {
name: "MCP Tool Card — expanded",
render: () => {
const data = dataWith([mcpCompleted])
return (
<StoryProviders data={data} sessionID={SESSION_ID}>
<div data-component="tool-part-wrapper" data-part-type="tool">
<Part part={mcpCompleted} message={baseAssistantMessage as any} defaultOpen />
</div>
</StoryProviders>
)
},
}
+25 -2
View File
@@ -56,7 +56,6 @@ import { useFileComponent } from "../context/file"
import { useDialog } from "../context/dialog"
import { useI18n } from "../context/i18n"
import { BasicTool } from "./basic-tool"
import { GenericTool } from "./basic-tool"
import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { Card } from "./card"
@@ -1120,6 +1119,30 @@ function ToolFileAccordion(props: { path: string; actions?: JSX.Element; childre
)
}
// kilocode_change start
function McpTool(props: ToolProps) {
return (
<BasicTool
icon="mcp"
status={props.status}
trigger={{ title: props.tool }}
hideDetails={props.hideDetails}
defaultOpen={props.defaultOpen}
forceOpen={props.forceOpen}
locked={props.locked}
>
<Show when={props.output}>
{(output) => (
<div data-component="tool-output" data-scrollable>
<Markdown text={output()} />
</div>
)}
</Show>
</BasicTool>
)
}
// kilocode_change end
PART_MAPPING["tool"] = function ToolPartDisplay(props) {
const i18n = useI18n()
const part = () => props.part as ToolPart
@@ -1136,7 +1159,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
// @ts-expect-error
const partMetadata = () => part().state?.metadata ?? emptyMetadata
const render = createMemo(() => ToolRegistry.render(part().tool) ?? GenericTool)
const render = createMemo(() => ToolRegistry.render(part().tool) ?? McpTool) // kilocode_change
// kilocode_change start
const dismissed = createMemo(() => {