diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
index 22d522e06c..ec1fd3d4db 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
@@ -520,4 +520,77 @@ describe('ToolConfirmationMessage', () => {
expect(output).toMatchSnapshot();
unmount();
});
+
+ it('should show MCP tool details expand hint for MCP confirmations', async () => {
+ const confirmationDetails: ToolCallConfirmationDetails = {
+ type: 'mcp',
+ title: 'Confirm MCP Tool',
+ serverName: 'test-server',
+ toolName: 'test-tool',
+ toolDisplayName: 'Test Tool',
+ toolArgs: {
+ url: 'https://www.google.co.jp',
+ },
+ toolDescription: 'Navigates browser to a URL.',
+ toolParameterSchema: {
+ type: 'object',
+ properties: {
+ url: {
+ type: 'string',
+ description: 'Destination URL',
+ },
+ },
+ required: ['url'],
+ },
+ onConfirm: vi.fn(),
+ };
+
+ const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
+ ,
+ );
+ await waitUntilReady();
+
+ const output = lastFrame();
+ expect(output).toContain('MCP Tool Details:');
+ expect(output).toContain('(press Ctrl+O to expand MCP tool details)');
+ expect(output).not.toContain('https://www.google.co.jp');
+ expect(output).not.toContain('Navigates browser to a URL.');
+ unmount();
+ });
+
+ it('should omit empty MCP invocation arguments from details', async () => {
+ const confirmationDetails: ToolCallConfirmationDetails = {
+ type: 'mcp',
+ title: 'Confirm MCP Tool',
+ serverName: 'test-server',
+ toolName: 'test-tool',
+ toolDisplayName: 'Test Tool',
+ toolArgs: {},
+ toolDescription: 'No arguments required.',
+ onConfirm: vi.fn(),
+ };
+
+ const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
+ ,
+ );
+ await waitUntilReady();
+
+ const output = lastFrame();
+ expect(output).toContain('MCP Tool Details:');
+ expect(output).toContain('(press Ctrl+O to expand MCP tool details)');
+ expect(output).not.toContain('Invocation Arguments:');
+ unmount();
+ });
});
diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
index c4e73b73f6..9a49e2aa5a 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
@@ -5,7 +5,7 @@
*/
import type React from 'react';
-import { useMemo, useCallback } from 'react';
+import { useMemo, useCallback, useState } from 'react';
import { Box, Text } from 'ink';
import { DiffRenderer } from './DiffRenderer.js';
import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
@@ -29,6 +29,7 @@ import { useKeypress } from '../../hooks/useKeypress.js';
import { theme } from '../../semantic-colors.js';
import { useSettings } from '../../contexts/SettingsContext.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
+import { formatCommand } from '../../utils/keybindingUtils.js';
import {
REDIRECTION_WARNING_NOTE_LABEL,
REDIRECTION_WARNING_NOTE_TEXT,
@@ -64,6 +65,17 @@ export const ToolConfirmationMessage: React.FC<
terminalWidth,
}) => {
const { confirm, isDiffingEnabled } = useToolActions();
+ const [mcpDetailsExpansionState, setMcpDetailsExpansionState] = useState<{
+ callId: string;
+ expanded: boolean;
+ }>({
+ callId,
+ expanded: false,
+ });
+ const isMcpToolDetailsExpanded =
+ mcpDetailsExpansionState.callId === callId
+ ? mcpDetailsExpansionState.expanded
+ : false;
const settings = useSettings();
const allowPermanentApproval =
@@ -86,9 +98,81 @@ export const ToolConfirmationMessage: React.FC<
[confirm, callId],
);
+ const mcpToolDetailsText = useMemo(() => {
+ if (confirmationDetails.type !== 'mcp') {
+ return null;
+ }
+
+ const detailsLines: string[] = [];
+ const hasNonEmptyToolArgs =
+ confirmationDetails.toolArgs !== undefined &&
+ !(
+ typeof confirmationDetails.toolArgs === 'object' &&
+ confirmationDetails.toolArgs !== null &&
+ Object.keys(confirmationDetails.toolArgs).length === 0
+ );
+ if (hasNonEmptyToolArgs) {
+ let argsText: string;
+ try {
+ argsText = stripUnsafeCharacters(
+ JSON.stringify(confirmationDetails.toolArgs, null, 2),
+ );
+ } catch {
+ argsText = '[unserializable arguments]';
+ }
+ detailsLines.push('Invocation Arguments:');
+ detailsLines.push(argsText);
+ }
+
+ const description = confirmationDetails.toolDescription?.trim();
+ if (description) {
+ if (detailsLines.length > 0) {
+ detailsLines.push('');
+ }
+ detailsLines.push('Description:');
+ detailsLines.push(stripUnsafeCharacters(description));
+ }
+
+ if (confirmationDetails.toolParameterSchema !== undefined) {
+ let schemaText: string;
+ try {
+ schemaText = stripUnsafeCharacters(
+ JSON.stringify(confirmationDetails.toolParameterSchema, null, 2),
+ );
+ } catch {
+ schemaText = '[unserializable schema]';
+ }
+ if (detailsLines.length > 0) {
+ detailsLines.push('');
+ }
+ detailsLines.push('Input Schema:');
+ detailsLines.push(schemaText);
+ }
+
+ if (detailsLines.length === 0) {
+ return null;
+ }
+
+ return detailsLines.join('\n');
+ }, [confirmationDetails]);
+
+ const hasMcpToolDetails = !!mcpToolDetailsText;
+ const expandDetailsHintKey = formatCommand(Command.SHOW_MORE_LINES);
+
useKeypress(
(key) => {
if (!isFocused) return false;
+ if (
+ confirmationDetails.type === 'mcp' &&
+ hasMcpToolDetails &&
+ keyMatchers[Command.SHOW_MORE_LINES](key)
+ ) {
+ setMcpDetailsExpansionState({
+ callId,
+ expanded: !isMcpToolDetailsExpanded,
+ });
+ return true;
+ }
if (keyMatchers[Command.ESCAPE](key)) {
handleConfirm(ToolConfirmationOutcome.Cancel);
return true;
@@ -100,7 +184,7 @@ export const ToolConfirmationMessage: React.FC<
}
return false;
},
- { isActive: isFocused },
+ { isActive: isFocused, priority: true },
);
const handleSelect = useCallback(
@@ -504,12 +588,31 @@ export const ToolConfirmationMessage: React.FC<
bodyContent = (
-
- MCP Server: {sanitizeForDisplay(mcpProps.serverName)}
-
-
- Tool: {sanitizeForDisplay(mcpProps.toolName)}
-
+ <>
+
+ MCP Server: {sanitizeForDisplay(mcpProps.serverName)}
+
+
+ Tool: {sanitizeForDisplay(mcpProps.toolName)}
+
+ >
+ {hasMcpToolDetails && (
+
+ MCP Tool Details:
+ {isMcpToolDetailsExpanded ? (
+ <>
+
+ (press {expandDetailsHintKey} to collapse MCP tool details)
+
+ {mcpToolDetailsText}
+ >
+ ) : (
+
+ (press {expandDetailsHintKey} to expand MCP tool details)
+
+ )}
+
+ )}
);
}
@@ -522,8 +625,17 @@ export const ToolConfirmationMessage: React.FC<
terminalWidth,
handleConfirm,
deceptiveUrlWarningText,
+ isMcpToolDetailsExpanded,
+ hasMcpToolDetails,
+ mcpToolDetailsText,
+ expandDetailsHintKey,
]);
+ const bodyOverflowDirection: 'top' | 'bottom' =
+ confirmationDetails.type === 'mcp' && isMcpToolDetailsExpanded
+ ? 'bottom'
+ : 'top';
+
if (confirmationDetails.type === 'edit') {
if (confirmationDetails.isModifying) {
return (
@@ -559,7 +671,7 @@ export const ToolConfirmationMessage: React.FC<
{bodyContent}
diff --git a/packages/cli/src/ui/keyMatchers.test.ts b/packages/cli/src/ui/keyMatchers.test.ts
index b2de83cd8b..763754ec95 100644
--- a/packages/cli/src/ui/keyMatchers.test.ts
+++ b/packages/cli/src/ui/keyMatchers.test.ts
@@ -352,7 +352,6 @@ describe('keyMatchers', () => {
createKey('l', { ctrl: true }),
],
},
-
// Shell commands
{
command: Command.REVERSE_SEARCH,
diff --git a/packages/core/src/confirmation-bus/types.ts b/packages/core/src/confirmation-bus/types.ts
index 69aa98832e..e02c773070 100644
--- a/packages/core/src/confirmation-bus/types.ts
+++ b/packages/core/src/confirmation-bus/types.ts
@@ -95,6 +95,9 @@ export type SerializableConfirmationDetails =
serverName: string;
toolName: string;
toolDisplayName: string;
+ toolArgs?: Record;
+ toolDescription?: string;
+ toolParameterSchema?: unknown;
}
| {
type: 'ask_user';
diff --git a/packages/core/src/tools/mcp-tool.ts b/packages/core/src/tools/mcp-tool.ts
index 6faa30c673..f80eebe272 100644
--- a/packages/core/src/tools/mcp-tool.ts
+++ b/packages/core/src/tools/mcp-tool.ts
@@ -80,6 +80,8 @@ export class DiscoveredMCPToolInvocation extends BaseToolInvocation<
readonly trust?: boolean,
params: ToolParams = {},
private readonly cliConfig?: Config,
+ private readonly toolDescription?: string,
+ private readonly toolParameterSchema?: unknown,
) {
// Use composite format for policy checks: serverName__toolName
// This enables server wildcards (e.g., "google-workspace__*")
@@ -123,6 +125,9 @@ export class DiscoveredMCPToolInvocation extends BaseToolInvocation<
serverName: this.serverName,
toolName: this.serverToolName, // Display original tool name in confirmation
toolDisplayName: this.displayName, // Display global registry name exposed to model and user
+ toolArgs: this.params,
+ toolDescription: this.toolDescription,
+ toolParameterSchema: this.toolParameterSchema,
onConfirm: async (outcome: ToolConfirmationOutcome) => {
if (outcome === ToolConfirmationOutcome.ProceedAlwaysServer) {
DiscoveredMCPToolInvocation.allowlist.add(serverAllowListKey);
@@ -317,6 +322,8 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool<
this.trust,
params,
this.cliConfig,
+ this.description,
+ this.parameterSchema,
);
}
}
diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts
index 608f405029..94188deca0 100644
--- a/packages/core/src/tools/tools.ts
+++ b/packages/core/src/tools/tools.ts
@@ -757,6 +757,9 @@ export interface ToolMcpConfirmationDetails {
serverName: string;
toolName: string;
toolDisplayName: string;
+ toolArgs?: Record;
+ toolDescription?: string;
+ toolParameterSchema?: unknown;
onConfirm: (outcome: ToolConfirmationOutcome) => Promise;
}