diff --git a/apps/code/src/renderer/components/permissions/McpPermission.tsx b/apps/code/src/renderer/components/permissions/McpPermission.tsx index 375926665..0bc12d1e5 100644 --- a/apps/code/src/renderer/components/permissions/McpPermission.tsx +++ b/apps/code/src/renderer/components/permissions/McpPermission.tsx @@ -8,7 +8,11 @@ import { 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"; +import { + type BasePermissionProps, + getMcpPermissionToolName, + toSelectorOptions, +} from "./types"; export function McpPermission({ toolCall, @@ -16,9 +20,7 @@ export function McpPermission({ onSelect, onCancel, }: BasePermissionProps) { - const mcpToolName = ( - toolCall._meta as { claudeCode?: { toolName?: string } } | undefined - )?.claudeCode?.toolName; + const mcpToolName = getMcpPermissionToolName(toolCall); if (!mcpToolName) { return ( diff --git a/apps/code/src/renderer/components/permissions/PermissionSelector.test.tsx b/apps/code/src/renderer/components/permissions/PermissionSelector.test.tsx new file mode 100644 index 000000000..7aabae858 --- /dev/null +++ b/apps/code/src/renderer/components/permissions/PermissionSelector.test.tsx @@ -0,0 +1,40 @@ +import { Theme } from "@radix-ui/themes"; +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { PermissionSelector } from "./PermissionSelector"; + +describe("PermissionSelector", () => { + it("renders MCP permissions using claudeCode.toolName metadata", () => { + render( + + + , + ); + + expect( + screen.getByText( + (_, element) => + element?.textContent === "posthog - Read execute-sql (MCP)", + ), + ).toBeInTheDocument(); + expect(screen.queryByText(/^exec$/)).not.toBeInTheDocument(); + }); +}); diff --git a/apps/code/src/renderer/components/permissions/PermissionSelector.tsx b/apps/code/src/renderer/components/permissions/PermissionSelector.tsx index b89ad00d0..74954d9af 100644 --- a/apps/code/src/renderer/components/permissions/PermissionSelector.tsx +++ b/apps/code/src/renderer/components/permissions/PermissionSelector.tsx @@ -11,7 +11,7 @@ import { ReadPermission } from "./ReadPermission"; import { SearchPermission } from "./SearchPermission"; import { SwitchModePermission } from "./SwitchModePermission"; import { ThinkPermission } from "./ThinkPermission"; -import type { PermissionToolCall } from "./types"; +import { getMcpPermissionToolName, type PermissionToolCall } from "./types"; interface PermissionSelectorProps { toolCall: PermissionToolCall; @@ -31,10 +31,8 @@ export function PermissionSelector({ onCancel, }: PermissionSelectorProps) { const props = { toolCall, options, onSelect, onCancel }; - const meta = toolCall._meta as - | { codeToolKind?: string; claudeCode?: { toolName?: string } } - | undefined; - const agentToolName = meta?.claudeCode?.toolName; + const meta = toolCall._meta as { codeToolKind?: string } | undefined; + const agentToolName = getMcpPermissionToolName(toolCall); if (agentToolName?.startsWith("mcp__")) { return ; } diff --git a/apps/code/src/renderer/components/permissions/types.ts b/apps/code/src/renderer/components/permissions/types.ts index ba7cd12c8..f09ec034b 100644 --- a/apps/code/src/renderer/components/permissions/types.ts +++ b/apps/code/src/renderer/components/permissions/types.ts @@ -22,6 +22,15 @@ export interface BasePermissionProps { onCancel: () => void; } +export function getMcpPermissionToolName( + toolCall: PermissionToolCall, +): string | undefined { + const toolName = ( + toolCall._meta as { claudeCode?: { toolName?: unknown } } | undefined + )?.claudeCode?.toolName; + return typeof toolName === "string" ? toolName : undefined; +} + export function toSelectorOptions( options: PermissionOption[], ): SelectorOption[] { diff --git a/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts b/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts index 15a3ee00b..dfcc425b1 100644 --- a/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts +++ b/packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts @@ -74,6 +74,33 @@ describe("canUseTool MCP approval enforcement", () => { expect.objectContaining({ toolCall: expect.objectContaining({ title: "The agent wants to call search_crm_objects (HubSpot)", + _meta: { + claudeCode: { toolName: "mcp__HubSpot__search_crm_objects" }, + }, + }), + }), + ); + }); + + it("passes metadata through generic PostHog exec approval requests", async () => { + setMcpToolApprovalStates({ + mcp__posthog__exec: "needs_approval", + }); + + const context = createContext("mcp__posthog__exec", { + toolInput: { command: "info execute-sql" }, + }); + const result = await canUseTool(context); + + expect(result.behavior).toBe("allow"); + expect(context.client.requestPermission).toHaveBeenCalledWith( + expect.objectContaining({ + toolCall: expect.objectContaining({ + rawInput: expect.objectContaining({ + command: "info execute-sql", + toolName: "mcp__posthog__exec", + }), + _meta: { claudeCode: { toolName: "mcp__posthog__exec" } }, }), }), ); diff --git a/packages/agent/src/adapters/claude/permissions/permission-handlers.ts b/packages/agent/src/adapters/claude/permissions/permission-handlers.ts index ec071bd93..6d1075b46 100644 --- a/packages/agent/src/adapters/claude/permissions/permission-handlers.ts +++ b/packages/agent/src/adapters/claude/permissions/permission-handlers.ts @@ -457,6 +457,7 @@ async function handleMcpApprovalFlow( ? [{ type: "content" as const, content: text(description) }] : [], rawInput: { ...(toolInput as Record), toolName }, + _meta: { claudeCode: { toolName } }, }, });