From 119bf4ced6577c3b6a167b73af002b2f89b88c0b Mon Sep 17 00:00:00 2001 From: Ephraim-9 Date: Thu, 5 Nov 2026 06:02:02 +0000 Subject: [PATCH 1/3] test(server): reproduce Claude declining a Yes/No MCP confirmation form mongodb-mcp-server asks for consent with a required confirmation enum ["Yes","No"] and no default; the Claude adapter finds no answer for it and declines before showing the approval card. --- .../Adapters/ClaudeAdapterV2.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index bc70c5bf7a161..307eaa75a83ce 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -1283,6 +1283,35 @@ describe("ClaudeAdapterV2 MCP elicitation acceptance", () => { } }); + // mongodb-mcp-server's Elicitation.CONFIRMATION_SCHEMA (@mongodb-js/mcp-core 3.2.2). + it("answers a Yes/No confirmation form with the affirmative choice", () => { + assert.deepEqual( + ClaudeAdapterV2.claudeMcpElicitationAcceptance( + form( + { + confirmation: { + type: "string", + title: "Would you like to confirm?", + description: "Would you like to confirm?", + enum: ["Yes", "No"], + enumNames: ["Yes, I confirm", "No, I do not confirm"], + }, + }, + ["confirmation"], + ), + ), + { action: "accept", content: { confirmation: "Yes" } }, + ); + for (const choice of ["yes", "Confirm", "Proceed", "OK", "continue"]) { + assert.deepEqual( + ClaudeAdapterV2.claudeMcpElicitationAcceptance( + form({ answer: { type: "string", enum: ["No", "Cancel", choice] } }, ["answer"]), + ), + { action: "accept", content: { answer: choice } }, + ); + } + }); + it("answers a persistence toggle with false", () => { assert.deepEqual( ClaudeAdapterV2.claudeMcpElicitationAcceptance( @@ -1314,6 +1343,10 @@ describe("ClaudeAdapterV2 MCP elicitation acceptance", () => { form({ grant: { type: "string", enum: ["always", "session"] } }, ["grant"]), form({ grant: { type: "string", enum: ["deny", "always"], default: "always" } }, ["grant"]), form({ limit: { type: "number", default: 500 } }, ["limit"]), + form({ confirmation: { type: "string", enum: ["No", "Cancel"], default: "No" } }, [ + "confirmation", + ]), + form({ grant: { type: "string", enum: ["No", "Yes always", "yes_for_session"] } }, ["grant"]), { serverName: "deploys", message: "Deploy web?", mode: "form" }, ] satisfies ReadonlyArray) { assert.isUndefined(ClaudeAdapterV2.claudeMcpElicitationAcceptance(request)); From 31688a3540df062c140c1ee50cf8f51e4f2af585 Mon Sep 17 00:00:00 2001 From: Ephraim-9 Date: Thu, 5 Nov 2026 06:05:14 +0000 Subject: [PATCH 2/3] fix(server): let Claude approve Yes/No MCP confirmation forms mongodb-mcp-server (and the hosted Atlas connector) confirms writes with a required "confirmation" enum ["Yes","No"]. No option matched the allow-once pattern, so the Claude adapter declined before any approval card appeared and the tool failed with "User did not confirm". When the user clicks Approve, the Claude path now also picks a one-time affirmative choice (Yes/Confirm/Proceed/Continue/OK). Schema defaults stay stripped and persistence choices are still never picked; the Codex path is unchanged since the option is opt-in. --- .../orchestration-v2/Adapters/ClaudeAdapterV2.ts | 1 + apps/server/src/provider/CodexMcpElicitation.ts | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 3c6295494ec83..f27b51f75496f 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -3855,6 +3855,7 @@ export function claudeMcpElicitationAcceptance( const response = toMcpElicitationResponse( { mode: "form", requestedSchema: withoutSchemaDefaults(request.requestedSchema) }, "accept", + { acceptConfirmationChoices: true }, ); // The helper leaves content out when the schema is not a form it can read. const filled = response.content; diff --git a/apps/server/src/provider/CodexMcpElicitation.ts b/apps/server/src/provider/CodexMcpElicitation.ts index eafe09e07e706..400cbeb33958f 100644 --- a/apps/server/src/provider/CodexMcpElicitation.ts +++ b/apps/server/src/provider/CodexMcpElicitation.ts @@ -177,9 +177,16 @@ export function describeMcpElicitation( }; } +const ONE_TIME_APPROVAL_CHOICE = + /^(?:once|accept|approve|allow)(?:[_\s-]*(?:once(?:[_\s-]*only)?|this[_\s-]*time))?$/i; +// Confirmation forms (e.g. mongodb-mcp-server's required "Yes"/"No" enum) ask +// for consent with a plain affirmative instead of an allow-once choice. +const ONE_TIME_CONFIRMATION_CHOICE = /^(?:yes|confirm|proceed|continue|ok(?:ay)?)$/i; + export function toMcpElicitationResponse( payload: McpElicitationResponseInput, decision: ProviderApprovalDecision, + responseOptions?: { readonly acceptConfirmationChoices?: boolean }, ): EffectCodexSchema.McpServerElicitationRequestResponse { if (decision === "decline" || decision === "cancel") { return { action: decision }; @@ -203,9 +210,10 @@ export function toMcpElicitationResponse( const chosenOption = options.find((option) => persist ? mcpElicitationPersistenceDecision(option.value) === decision - : /^(?:once|accept|approve|allow)(?:[_\s-]*(?:once(?:[_\s-]*only)?|this[_\s-]*time))?$/i.test( - option.value.trim(), - ) && mcpElicitationPersistenceDecision(option.value) === null, + : (ONE_TIME_APPROVAL_CHOICE.test(option.value.trim()) || + (responseOptions?.acceptConfirmationChoices === true && + ONE_TIME_CONFIRMATION_CHOICE.test(option.value.trim()))) && + mcpElicitationPersistenceDecision(option.value) === null, ); if (chosenOption) { content[key] = chosenOption.value; From 12711ebbc8204f5c0df3f50b8e7453360f842453 Mon Sep 17 00:00:00 2001 From: Ephraim-9 Date: Thu, 5 Nov 2026 06:16:52 +0000 Subject: [PATCH 3/3] fix(server): never auto-confirm a persistence field with a Yes choice A required Yes/No field titled like "Always allow this application" would have been answered Yes, granting persistent access. Confirmation choices now skip persistence fields. --- .../src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts | 6 ++++++ apps/server/src/provider/CodexMcpElicitation.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index 307eaa75a83ce..0e7ba5f227e88 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -1347,6 +1347,12 @@ describe("ClaudeAdapterV2 MCP elicitation acceptance", () => { "confirmation", ]), form({ grant: { type: "string", enum: ["No", "Yes always", "yes_for_session"] } }, ["grant"]), + form( + { + remember: { type: "string", title: "Always allow this application", enum: ["Yes", "No"] }, + }, + ["remember"], + ), { serverName: "deploys", message: "Deploy web?", mode: "form" }, ] satisfies ReadonlyArray) { assert.isUndefined(ClaudeAdapterV2.claudeMcpElicitationAcceptance(request)); diff --git a/apps/server/src/provider/CodexMcpElicitation.ts b/apps/server/src/provider/CodexMcpElicitation.ts index 400cbeb33958f..07af9a7d0f904 100644 --- a/apps/server/src/provider/CodexMcpElicitation.ts +++ b/apps/server/src/provider/CodexMcpElicitation.ts @@ -212,6 +212,7 @@ export function toMcpElicitationResponse( ? mcpElicitationPersistenceDecision(option.value) === decision : (ONE_TIME_APPROVAL_CHOICE.test(option.value.trim()) || (responseOptions?.acceptConfirmationChoices === true && + !isMcpElicitationPersistenceField(key, field) && ONE_TIME_CONFIRMATION_CHOICE.test(option.value.trim()))) && mcpElicitationPersistenceDecision(option.value) === null, );