fix(server): show Claude MCP elicitations on the approval card#21903
2 unresolved threads · Jump to file
- apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:2674–2693 · **🎯 Functional Correctness** | **🟡 Minor** | **⚡ Quick win** <details> <summary>🔎 Supported by s
- apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:2654–2673 · Fixed in cac644c5d7. The Claude path now drops `default` from every field before filling the form, s
0 / 3 viewed3 files changedLine totals unavailable
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts · Earlier revision db9449db · right 2654–2673
Original diff
@@ -2617,6 +2623,56 @@ export const awaitClaudeApprovalDecision = Effect.fn("awaitClaudeApprovalDecisio
return yield* Effect.raceFirst(Deferred.await(decision), cancellation);
});
+// Web and mobile offer a session grant when options are omitted, and this
+// path only ever answers once.
+const CLAUDE_MCP_ELICITATION_OPTIONS: ReadonlyArray<ProviderApprovalOption> = [
+ { decision: "cancel", label: "Cancel" },
+ { decision: "decline", label: "Decline" },
+ { decision: "accept", label: "Approve" },
+];
+
+type ClaudeMcpElicitationContent = NonNullable<ElicitationResult["content"]>;
+
+const isClaudeMcpElicitationValue = (
+ value: unknown,
+): value is ClaudeMcpElicitationContent[string] =>
+ typeof value === "string" ||
+ typeof value === "number" ||
+ typeof value === "boolean" ||
+ (Array.isArray(value) && value.every((item) => typeof item === "string"));
+
+/**
+ * The answer an Approve click sends, or undefined when the card cannot answer
+ * the form. The card only collects consent, so the form is filled the way
+ * Codex fills it (approval choices and defaults) and anything left unfilled,
+ * URL mode included, is declined before a card is shown.
+ */
+export function claudeMcpElicitationAcceptance(
+ request: ElicitationRequest,
+): ElicitationResult | undefined {
+ if (request.mode === "url" || request.requestedSchema === undefined) return undefined;
+ const response = toMcpElicitationResponse(
+ { mode: "form", requestedSchema: request.requestedSchema },
+ "accept",
+ );
+ // The helper leaves content out when the schema is not a form it can read.
+ const filled = response.content;
+ if (
+ response.action !== "accept" ||
+ typeof filled !== "object" ||
+ filled === null ||
+ Array.isArray(filled)
+ ) {
+ return undefined;
+ }
+ const content: ClaudeMcpElicitationContent = {};
+ for (const [key, value] of Object.entries(filled)) {
+ if (!isClaudeMcpElicitationValue(value)) return undefined;
+ content[key] = value;
+ }
+ return { action: "accept", content };apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts · right 2654–2673
chrischriscris5d ago
Fixed in cac644c5d7. The Claude path now drops default from every field before filling the form, so Approve only sends a one-time choice or a false persistence toggle. A required field that only a default could fill is declined with no card, and an optional one is left out.