diff --git a/src/codex/codexMcpClient.ts b/src/codex/codexMcpClient.ts index 0c7097d1..8b98b928 100644 --- a/src/codex/codexMcpClient.ts +++ b/src/codex/codexMcpClient.ts @@ -7,9 +7,9 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { logger } from '@/ui/logger'; import type { CodexSessionConfig, CodexToolResponse } from './types'; import { z } from 'zod'; -import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { CodexPermissionHandler } from './utils/permissionHandler'; import { execSync } from 'child_process'; +import { randomUUID } from 'node:crypto'; const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1000; // 14 days, which is the half of the maximum possible timeout (~28 days for int32 value in NodeJS) @@ -107,28 +107,68 @@ export class CodexMcpClient { } private registerPermissionHandlers(): void { + // Codex uses MCP `elicitation/create` for approvals, but augments params with `codex_*` fields. + // The upstream SDK schema for ElicitRequest is strict and drops unknown fields, which breaks + // our ability to read `codex_call_id` / `codex_command` / `codex_changes`. + const CodexElicitationCreateRequestSchema = z.object({ + method: z.literal('elicitation/create'), + params: z.object({ + message: z.string(), + requestedSchema: z.any().optional(), + + codex_elicitation: z.string().optional(), + codex_mcp_tool_call_id: z.string().optional(), + codex_event_id: z.string().optional(), + codex_call_id: z.string().optional(), + codex_command: z.array(z.string()).optional(), + codex_cwd: z.string().optional(), + codex_parsed_cmd: z.any().optional(), + codex_changes: z.record(z.string(), z.any()).optional(), + }).passthrough(), + }).passthrough(); + // Register handler for exec command approval requests this.client.setRequestHandler( - ElicitRequestSchema, + CodexElicitationCreateRequestSchema, async (request) => { - console.log('[CodexMCP] Received elicitation request:', request.params); - - // Load params - const params = request.params as unknown as { - message: string, - codex_elicitation: string, - codex_mcp_tool_call_id: string, - codex_event_id: string, - codex_call_id: string, - codex_command: string[], - codex_cwd: string - } - const toolName = 'CodexBash'; + const params = request.params; + logger.debug('[CodexMCP] Received elicitation request:', params); + + const toolCallId = params.codex_call_id || params.codex_event_id || params.codex_mcp_tool_call_id || randomUUID(); + const codexElicitationType = params.codex_elicitation || 'unknown'; + + const toolName = (() => { + switch (codexElicitationType) { + case 'exec-approval': + return 'CodexBash'; + case 'patch-approval': + return 'CodexPatch'; + default: + return 'CodexElicitation'; + } + })(); + + const toolInput = (() => { + switch (codexElicitationType) { + case 'exec-approval': + return { + command: params.codex_command, + cwd: params.codex_cwd + }; + case 'patch-approval': + return { + changes: params.codex_changes + }; + default: + return params; + } + })(); // If no permission handler set, deny by default if (!this.permissionHandler) { logger.debug('[CodexMCP] No permission handler set, denying by default'); return { + action: 'decline' as const, decision: 'denied' as const, }; } @@ -136,21 +176,26 @@ export class CodexMcpClient { try { // Request permission through the handler const result = await this.permissionHandler.handleToolCall( - params.codex_call_id, + toolCallId, toolName, - { - command: params.codex_command, - cwd: params.codex_cwd - } + toolInput ); logger.debug('[CodexMCP] Permission result:', result); + + const action = result.decision === 'approved' || result.decision === 'approved_for_session' + ? 'accept' as const + : result.decision === 'denied' + ? 'decline' as const + : 'cancel' as const; return { + action, decision: result.decision } } catch (error) { logger.debug('[CodexMCP] Error handling permission request:', error); return { + action: 'decline' as const, decision: 'denied' as const, reason: error instanceof Error ? error.message : 'Permission request failed' };