diff --git a/apps/code/src/renderer/components/action-selector/ActionSelector.tsx b/apps/code/src/renderer/components/action-selector/ActionSelector.tsx index bd039e457..21cb94f8e 100644 --- a/apps/code/src/renderer/components/action-selector/ActionSelector.tsx +++ b/apps/code/src/renderer/components/action-selector/ActionSelector.tsx @@ -228,11 +228,19 @@ export function ActionSelector({ /> )} - {title && ( - - {compactHomePath(title)} - - )} + {title && + (typeof title === "string" ? ( + + {compactHomePath(title)} + + ) : ( + + {title} + + ))} {pendingAction && {pendingAction}} diff --git a/apps/code/src/renderer/components/action-selector/types.ts b/apps/code/src/renderer/components/action-selector/types.ts index 209a57c0e..b4b7acfcf 100644 --- a/apps/code/src/renderer/components/action-selector/types.ts +++ b/apps/code/src/renderer/components/action-selector/types.ts @@ -18,7 +18,7 @@ export interface StepAnswer { } export interface ActionSelectorProps { - title: string; + title: ReactNode; pendingAction?: ReactNode; question: ReactNode; options: SelectorOption[]; diff --git a/apps/code/src/renderer/components/permissions/McpPermission.tsx b/apps/code/src/renderer/components/permissions/McpPermission.tsx new file mode 100644 index 000000000..99dedb320 --- /dev/null +++ b/apps/code/src/renderer/components/permissions/McpPermission.tsx @@ -0,0 +1,70 @@ +import { ActionSelector } from "@components/ActionSelector"; +import { parseMcpToolKey } from "@features/mcp-apps/utils/mcp-app-host-utils"; +import { + getPostHogExecDisplay, + isPostHogExecTool, +} from "@features/mcp-apps/utils/posthog-exec-display"; +import { formatInput } from "@features/sessions/components/session-update/toolCallUtils"; +import { Box, Code } from "@radix-ui/themes"; +import { DefaultPermission } from "./DefaultPermission"; +import { type BasePermissionProps, toSelectorOptions } from "./types"; + +export function McpPermission({ + toolCall, + options, + onSelect, + onCancel, +}: BasePermissionProps) { + const mcpToolName = ( + toolCall._meta as { claudeCode?: { toolName?: string } } | undefined + )?.claudeCode?.toolName; + + if (!mcpToolName) { + return ( + + ); + } + + const { serverName: defaultServerName, toolName: defaultToolName } = + parseMcpToolKey(mcpToolName); + const posthogDisplay = isPostHogExecTool(mcpToolName) + ? getPostHogExecDisplay(toolCall.rawInput) + : null; + const serverName = posthogDisplay ? "posthog" : defaultServerName; + const toolName = posthogDisplay?.label ?? defaultToolName; + const fullInput = formatInput(toolCall.rawInput); + + return ( + + {serverName} + {" - "} + {toolName} + {" (MCP)"} + + } + pendingAction={ + fullInput ? ( + + + {fullInput} + + + ) : undefined + } + question="Do you want to proceed?" + options={toSelectorOptions(options)} + onSelect={onSelect} + onCancel={onCancel} + /> + ); +} diff --git a/apps/code/src/renderer/components/permissions/PermissionSelector.stories.tsx b/apps/code/src/renderer/components/permissions/PermissionSelector.stories.tsx index fe3fc4cb8..2db161002 100644 --- a/apps/code/src/renderer/components/permissions/PermissionSelector.stories.tsx +++ b/apps/code/src/renderer/components/permissions/PermissionSelector.stories.tsx @@ -477,6 +477,57 @@ export const Default: Story = { }, }; +function buildMcpToolCallData( + mcpToolName: string, + rawInput: Record, +) { + return { + toolCallId: `story-${Date.now()}`, + title: mcpToolName.split("__").slice(2).join("__") || mcpToolName, + kind: "other" as const, + rawInput, + content: [], + _meta: { claudeCode: { toolName: mcpToolName } }, + }; +} + +const posthogExecInput = { + command: 'call execute-sql {"query":"select 1"}', +}; +export const McpPostHogExec: Story = { + args: { + toolCall: buildMcpToolCallData("mcp__posthog__exec", posthogExecInput), + options: buildPermissionOptions("mcp__posthog__exec", posthogExecInput), + }, +}; + +const githubIssueInput = { + owner: "PostHog", + repo: "posthog", + title: "Investigate intermittent flake in foo test", + body: "Seen on CI runs 12345 and 67890 — appears related to fixture cleanup ordering.", + labels: ["bug", "ci"], +}; +export const McpGithubCreateIssue: Story = { + args: { + toolCall: buildMcpToolCallData( + "mcp__github__create_issue", + githubIssueInput, + ), + options: buildPermissionOptions( + "mcp__github__create_issue", + githubIssueInput, + ), + }, +}; + +export const McpNoArgs: Story = { + args: { + toolCall: buildMcpToolCallData("mcp__example__ping", {}), + options: buildPermissionOptions("mcp__example__ping", {}), + }, +}; + const exitPlanModeInput = { plan: `# Add Dark Mode Support diff --git a/apps/code/src/renderer/components/permissions/PermissionSelector.tsx b/apps/code/src/renderer/components/permissions/PermissionSelector.tsx index bd4f1de54..b89ad00d0 100644 --- a/apps/code/src/renderer/components/permissions/PermissionSelector.tsx +++ b/apps/code/src/renderer/components/permissions/PermissionSelector.tsx @@ -4,6 +4,7 @@ import { DeletePermission } from "./DeletePermission"; import { EditPermission } from "./EditPermission"; import { ExecutePermission } from "./ExecutePermission"; import { FetchPermission } from "./FetchPermission"; +import { McpPermission } from "./McpPermission"; import { MovePermission } from "./MovePermission"; import { QuestionPermission } from "./QuestionPermission"; import { ReadPermission } from "./ReadPermission"; @@ -30,9 +31,14 @@ export function PermissionSelector({ onCancel, }: PermissionSelectorProps) { const props = { toolCall, options, onSelect, onCancel }; - const codeToolKind = (toolCall._meta as { codeToolKind?: string } | undefined) - ?.codeToolKind; - const kind = codeToolKind ?? (toolCall.kind as string); + const meta = toolCall._meta as + | { codeToolKind?: string; claudeCode?: { toolName?: string } } + | undefined; + const agentToolName = meta?.claudeCode?.toolName; + if (agentToolName?.startsWith("mcp__")) { + return ; + } + const kind = meta?.codeToolKind ?? (toolCall.kind as string); switch (kind) { case "execute":