Skip to content
Source
Sign inSign up

fix(server): Claude MCP forms that ask for values open a question card instead of being declined #35998

Opennaxvog(GitHub profile, opens in a new tab)wants to merge 4 commits intomain fromrefs/pull/35998/headopened View on GitHub

naxvog(GitHub profile, opens in a new tab)commented

Problem

On Claude threads, an MCP elicitation form with a required value field (string, number, enum, multi-select) is declined in a few milliseconds without any UI, and the MCP server is told the user refused. Forms that gate an action on a typed confirmation code cannot be used at all. ACP providers already show the same forms as a question card.

Change

When the approval card cannot answer a form (claudeMcpElicitationAcceptance returns undefined), the Claude adapter now parses it with the ACP adapter's parseElicitationForm and opens the existing user_input question card. Headers are prefixed with the MCP server's name so the card is not mistaken for Claude's own question. Answers go back typed as accept; dismissal, abort or a missing required answer return cancel, as on ACP.

Unchanged: empty confirms, allow-style approvals (session grants) and forms with only optional fields stay on the approval card with Decline. URL mode still declines.

Supporting changes: parseElicitationForm is exported from @t3tools/provider-acp; it now falls back to the question header when the message is blank, since question must be non-empty. The AskUserQuestion wait is moved into a shared helper used by both paths.

Scope

Fixes #35996. The approval-card scope from #14878 / #21903 is kept as is; this only covers forms that are declined today.

Verification

  • Reproduced with a test MCP server that sends one form per elicitation type against desktop 0.1.9 and main (0a333a69): every form with a required value was declined in 2–9 ms with no card.

  • With this change, built from source and driven through real Claude turns: string, string formats, integer, number, enum (plain, titled, legacy), multi-select and combined forms opened a question card, and the server received typed content, e.g. {"code":713321} for a required integer, {"toppings":["olives","cheese"]} for a bounded multi-select. Empty confirm, optional-only forms and the allow/deny form still opened the approval card. URL mode still declined.

  • New adapter tests (value form accepted with typed content and server-prefixed header; missing required answer cancels; abort withdraws the card and rejects a late answer) fail on main and pass here. A routing test pins allow/deny forms to the approval card.

  • vp run check:changed: format, lint, knip, typecheck (41 packages) and tests pass.

  • Screenshots (web, same MCP tool and prompt; a required integer field with "Type NNNNNN to confirm." in its description):

    Before (main)After: cardAfter: result
    t3-code_elicitation_beforet3-code_elicitation_after1t3-code_elicitation_after2

    Before: no card, the tool gets decline. After: the question card names the MCP server, and the typed code reaches the server as {"code":558814}.

  • Not checked: mobile rendering of the question card for these forms, Codex (CodexMcpElicitation.ts still declines value forms).

Implemented with Claude Opus 5.5 in Claude Code via T3 Code; reviewed by Claude Opus 5.5 and Claude Fable 5.2 subagents.

    1. github-actions[bot](GitHub profile, opens in a new tab) added labelsvouch:unvouched

    2. tanstack-source added labelsbug

    3. Approvability

      Verdict: Approved at 172d9a2

      Macroscope's review found this PR approvable — Supported Claude MCP forms now open question cards and return the user's submitted answers rather than being declined. Existing cancellation handling and schema-based answer conversion limit the change to interactive requests.

      You can add or adjust custom eligibility rules. [Learn more](https://docs.macroscope.com/approvability#example-custom-eligibility-rules).
    4. Explain Change →

      No actionable comments were generated in the recent review.

      Walkthrough

      Claude MCP form elicitations that parse into questions now use user-input requests. Submitted answers return as form content; aborted requests or answers that produce no content return a cancel response. The shared request lifecycle also handles AskUserQuestion, and blank form messages now fall back to question headers.

      Changes: MCP form elicitation

      Layer / File(s)Summary
      Form question text and export
      packages/provider-acp/src/server/elicitationForm.ts, packages/provider-acp/src/server/elicitationForm.test.ts, packages/provider-acp/package.json
      Question text falls back from the trimmed description to the trimmed message, then to the question header. The package exports the parser, and a test covers blank messages.
      Shared user-input request lifecycle
      apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
      A helper publishes a user-input request, waits for answers or cancellation, removes pending state, and cancels withdrawn requests. AskUserQuestion uses this helper.
      Claude MCP form routing
      apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
      Eligible form elicitations use user-input questions and return parsed content after answers. Tests cover value answers, approval choices, missing required answers, and unaskable forms.

      Estimated code review effort: 3 (Moderate) | ~20 minutes

      Change: Bug fix · Severity of issue fixed: Medium

      Sequence Diagram(s)

      sequenceDiagram
        participant MCPServer
        participant ClaudeAdapterV2
        participant parseElicitationForm
        participant awaitUserInputRequest
        participant User
        MCPServer->>ClaudeAdapterV2: Send form elicitation
        ClaudeAdapterV2->>parseElicitationForm: Parse requested schema
        parseElicitationForm-->>ClaudeAdapterV2: Return questions
        ClaudeAdapterV2->>awaitUserInputRequest: Publish questions and wait
        awaitUserInputRequest->>User: Show user-input request
        User-->>awaitUserInputRequest: Submit answers
        awaitUserInputRequest-->>ClaudeAdapterV2: Return answers
        ClaudeAdapterV2-->>MCPServer: Return parsed form content

      Possibly related PRs

      • pingdotgg/t3code#21903: Adds Claude MCP elicitation handling on the approval card; this change routes supported value forms to user-input questions.
      • pingdotgg/t3code#33575: Changes responses for elicitation forms that cannot be shown; this change prompts for supported values and returns cancel when answers are unavailable.

      Suggested labels: bugfix

      Suggested reviewers: juliusmarminge

      Merge Risk: Minimal · up to 766fc

      Claude MCP forms that need typed values now open a question card, and the entered answers are returned to the server. Dismissal or a missing answer cancels the request. No remaining concrete merge-blocking risk was found. Mobile rendering and live Claude-provider behavior were not verified.

      Pre-merge checks: Passed 4

      Check nameStatusExplanation
      Title checkPassedThe title names the Claude MCP elicitation change: forms that need values now open a question card instead of being declined. The changeset matches this: the Claude adapter parses required-value forms…
      Description checkPassedThe description follows the repository template. It has Problem, Change, Scope, and Verification sections. The Problem section gives the expected behavior and reproduction environment (desktop 0.1.9 a…
      Linked Issues checkPassed[#35996] Requirement: Claude MCP form elicitations with required value fields must open a question card and return the typed value, not decline without a prompt. The change summary for ClaudeAdapterV2…
      Out of Scope Changes checkPassedEach changed area connects to #35996. ClaudeAdapterV2.ts adds the form routing and moves the AskUserQuestion wait into the shared helper, which the new path also uses. `packages/provider-acp/package.j…
      Full details: Title check

      Explanation

      The title names the Claude MCP elicitation change: forms that need values now open a question card instead of being declined. The changeset matches this: the Claude adapter parses required-value forms with parseElicitationForm and opens the user_input card, and the tests cover typed acceptance, missing-answer cancellation, and abort. The title follows the conventional commit format used in the repository template. It is slightly long at 96 characters, but it is specific and accurate.

      Full details: Description check

      Explanation

      The description follows the repository template. It has Problem, Change, Scope, and Verification sections. The Problem section gives the expected behavior and reproduction environment (desktop 0.1.9 and main 0a333a69). The Change section explains the fix and the supporting export and fallback changes. The Scope section links issue #35996 and states the approval-card boundary. The Verification section lists focused adapter tests, the vp run check:changed results, before/after screenshots, and items not checked (mobile rendering, Codex). It also names the model and harness used, as the template asks. The template's intro comment is unfilled, which is expected for a template comment and does not count against the description.

      Full details: Linked Issues check

      Explanation

      [#35996] Requirement: Claude MCP form elicitations with required value fields must open a question card and return the typed value, not decline without a prompt. The change summary for ClaudeAdapterV2.ts states that when claudeMcpElicitationAcceptance cannot answer and the mode is not URL, the adapter calls parseElicitationForm, builds a user_input request with the server label in each question header, and returns accept with parsed content. It returns cancel when the request is aborted or no content can be produced. The shared awaitUserInputRequest helper handles waiting and cleanup. Test summaries cover typed string acceptance, allow/deny routing to the approval card, and missing-answer cancellation. Forms with an existing acceptance path keep it. URL mode still declines, which matches the issue scope (#33211). The incremental diff since the previous reviewed heads did not return file content. The assessment therefore relies on the change summary and the prior whole-PR review, which both support the routing. No unmet coding requirement was found.

      Full details: Out of Scope Changes check

      Explanation

      Each changed area connects to #35996. ClaudeAdapterV2.ts adds the form routing and moves the AskUserQuestion wait into the shared helper, which the new path also uses. packages/provider-acp/package.json adds the ./server/elicitationForm export so the Claude adapter can import parseElicitationForm. elicitationForm.ts now falls back to the next question header when the message is blank, because question must be non-empty. This also changes ACP parsing for blank messages only, and a test covers it. The test changes in ClaudeAdapterV2.test.ts and elicitationForm.test.ts support the objective. The incremental diff did not show file content, so this result relies on the change summary and the whole-PR review. No unrelated change was found.

      Finishing Touches

      Generate unit tests (beta)

      • Create a new PR
      • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
      Recent review info

      Run configuration

      Commits

      Reviewing files that changed from the base of the PR and between 14b8aa7b72d8083b48ddbc24e4d0ef09c258e00a and 766fc584e3d1ef7a1d5df55d8abad02d8bd1f233.

      Files selected for processing: 2

      • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
      • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts

      Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

    5. Additional comments: 1

      apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts-11757-11757: 1

      11757-11757: Minor · Data Integrity & Integration · Quick win

      **Unverified finding** Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

      Attach the form request to an existing parent node.

      buildApprovalRequestArtifacts derives parentNodeId from this synthetic nativeItemId at Lines 8311-8316. This branch never creates that node, so the emitted request has an absent parent in the execution graph.

      Pass parentNodeId: context.input.rootNodeId, as the control-request path does at Lines 11572-11574, or attach the request to the actual eliciting tool node.

      Review info

      Run configuration

      Commits

      Reviewing files that changed from the base of the PR and between 43ce39595bfcea4af4b99b1fa77c7e1a73256da2 and 172d9a2d41f3b12fdab514b25bd9136e4668fc69.

      Files selected for processing: 5

      • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
      • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
      • packages/provider-acp/package.json
      • packages/provider-acp/src/server/elicitationForm.test.ts
      • packages/provider-acp/src/server/elicitationForm.ts

      Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

    6. juliusmarmingecommented

      The automated pipeline stopped on this pull request and will not merge it:

      the fix is written and reviewed, but proving it needs something no worker has:

      • Permanent blocker: the reproduction needs a working Claude provider session; first-party Claude access is listed as unavailable on this VM. Replay speaks generic ACP and cannot exercise Claude's MCP elicitation callback. Immediate decline on base, the question card and typed accept response on head, and dismissal/stop cancellation remain unverified.

      It stays open for a person to finish or close.

Sign in to join this conversation.