fix(server): MCP App forms now show when a write tool defers to them#23850

open3
main
GitHub fork: fix/mcp-app-deferred-forms
opened6d agobySshivam
View on GitHubTwo-way sync enabledPR last imported 16h ago

Problem

GitHub's MCP server (and any server built the same way) hands write tools like create_pull_request off to an MCP App form. It answers the first call with an isError result whose status is awaiting_user_submission, and expects the host to show the form. Codex reports an isError result as a failed call, and T3 only captured and showed MCP apps for completed calls. So the form never appeared, and the write could never happen.

Change

  • The Codex adapter captures the app whenever the call has a result, not only when it completed, and passes isError: true on to the app for a failed call.
  • Web and mobile show a captured app whatever the call's status. Only the adapter attaches an app, and only once the call has a result.

Clicking submit in the form goes through the existing confirmation prompt for app tool calls, then the write runs.

Only Codex is affected. GitHub's server defers only when the client's initialize carries the io.modelcontextprotocol/ui extension (shouldDeferToForm). Claude Code 2.1.308 doesn't send it, so writes run directly there. What the test server logged from each client's initialize:

1claude-code ui extension: no
2codex (via T3) ui extension: yes

Scope and approval

A small fix for an obvious bug reported in #23825. MCP Apps already render inline in threads (#16236). This only stops a Codex isError result from hiding an app that the server asked the host to show. No new settings or workflows, and the existing confirm prompt still guards app-initiated writes.

Verification

Recordings (web, Codex, current head)

Before (head with the three fix files reverted to the PR base): the agent says the form is open, but the thread shows only a "Create note" tool row and no form.

https://github.com/user-attachments/assets/ae7879d4-dd5a-4350-bec5-016c7492372c

After (head 335e7e2fdf): the form shows with the title the agent passed in. Clicking "Create note" asks to allow the call; after Confirm the server creates the note and the form reads Created note "Ship the fix".

https://github.com/user-attachments/assets/65a685d5-6b0d-47aa-809b-b9c3ca7c2a36

Testing for the recordings: head 335e7e2fdf, run on corex (Linux) with vp run dev --no-seed --home-dir <throwaway home>, Codex with the test notes MCP server described below, headless Chromium driven by Playwright with video recording, converted to MP4. Same prompt both runs: Use the notes create_note tool once to create a note titled "Ship the fix". Server log for the after run:

1tools/call create_note {"title":"Ship the fix"} -> isError, awaiting_user_submission
2resources/read ui://notes/create-note.html
3tools/call create_note {"title":"Ship the fix","_ui_submitted":true} -> Created note "Ship the fix".

Before / after (Linux, web UI, Codex)

I reproduced this with a small stdio MCP server that defers the same way GitHub's does (isError + awaiting_user_submission unless the call carries _ui_submitted: true).

Before (main): the call shows as a plain tool row. No form.

before

After: the form shows up with the title the agent passed in.

after: form shown

Clicking "Create note" asks for confirmation. After confirming, the server gets the _ui_submitted call and creates the note. Server log for the turn:

1tools/call create_note {"title":"Ship the fix"} -> isError, awaiting_user_submission
2tools/call create_note {"title":"Ship the fix","_ui_submitted":true} -> Created note "Ship the fix".

after: submitted

Tests

  • CodexAdapterV2.test.ts: a failed call with a result still gets its app captured, and the app sees isError: true. Fails on main.
  • session-logic.test.ts: the web timeline shows the app for a failed call.

With GitHub's real MCP server (web, Codex)

github-mcp-server v2.0.3 (stdio, --toolsets=issues) against a throwaway private repo. The first issue_write call came back as GitHub's deferral (isError: true, "An interactive form has been shown to the user… The issue has NOT been created yet") and created nothing.

Before (fix reverted): no form, and no issue gets created.

real server before

After: GitHub's "Create issue" form shows in the thread.

real server form

Clicking "Create issue" asks for confirmation with the _ui_submitted: true call, and after confirming GitHub created the issue.

real server submitted

Mobile (Android, POCO F4, Codex, GitHub's real server)

A new thread started from the phone: the form shows, "Create issue" asks to allow the call, and GitHub created the issue. With only the mobile line reverted (server fix kept), the same thread shows no form, so the mobile change is needed.

FormSubmittedMobile line reverted
mobile formmobile submittedmobile reverted

Mobile recordings (iOS and Android, Codex, current head 335e7e2fdf)

Same test notes server and prompt as the web recordings. Each clip starts on the thread list, the turn is started from the server CLI, then the new thread is opened. "Before" is the head with the three fix files (CodexAdapterV2.ts, apps/mobile/src/lib/threadActivity.ts, apps/web/src/session-logic.ts) from the PR base and the backend restarted.

iOS before (iPhone 17 simulator): the agent says to submit the form, but there's no form. Expanding "Worked for 12s" shows only a failed "Create note" tool row.

https://github.com/user-attachments/assets/6bb865ab-dd8e-4910-8fba-7d72b1aa9286

iOS after: the form shows with the agent's title. "Create note" asks to allow the call; after Allow the form reads Created note "Ship the fix".

https://github.com/user-attachments/assets/a2bb44e0-96d2-446a-af75-dfd83da1681b

Android before (POCO F4): same as iOS, a failed "Create note" tool row and no form.

https://github.com/user-attachments/assets/ab65b1dd-99db-49ee-ba51-94375a24aec7

Android after: the form shows, "Create note" asks to allow the call, and after Allow it reads Created note "Ship the fix".

https://github.com/user-attachments/assets/c7149822-93db-43a8-aec4-f662cb16fcfc

Test server log across the four runs: the before runs logged only the deferred call (isError, awaiting_user_submission) with no resources/read; each after run logged the deferred call, resources/read ui://notes/create-note.html, then the _ui_submitted: true call that created the note.

Testing for the mobile recordings: head 335e7e2fdf, backend (node apps/server/src/bin.ts serve --base-dir <throwaway home> with a throwaway CODEX_HOME that registers the stdio test server) and Metro (APP_VARIANT=development expo start --dev-client --port 8262) from the PR worktree on corex (Linux). Threads started with t3 thread start --provider codex --model gpt-6.1-sol. Android: POCO F4 (Android 14) with the existing T3 Code Dev client over adb reverse, recorded on the phone. iOS: iPhone 17 simulator on shiv (macOS) with the existing T3 Code Dev client, backend and Metro reached through ssh -R, recorded with xcrun simctl io recordVideo.

Not covered

After a page reload, a submitted thread shows the empty form again, because the app keeps its "submitted" state only in memory and T3 doesn't save app state. The confirm prompt still guards a second submit. Fixed in the stacked #24208.

Closes #23825

Opus 5.5, Claude Code in T3 Code.

🤖 Generated with Claude Code

Checks

22 successful · 11 skipped
All checks passed
7623a95

Added 1 commit

  • fix(server): show MCP app forms when a write tool defers to them335e7e2
Synced from GitHub

Approvability

Verdict: Approved at 335e7e2

Macroscope's review found this PR approvable — This is a focused MCP App rendering fix: Codex-deferred write calls marked failed can now retain their app form, while unrelated failed tools remain ordinary tool rows. The server and web behavior are covered by targeted tests, with the mobile change being the same narrow presentation condition.

No code changes detected at 7623a95. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between c4d269f75a09b0bfa15829f28c0c5a97e069cf28 and 7623a95a8d8c42d9940bb51ff79c19e45dfebc49.

External image Files selected for processing: 5
  • apps/mobile/src/lib/threadActivity.ts
  • apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
  • apps/web/src/session-logic.test.ts
  • apps/web/src/session-logic.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.

Walkthrough

The Codex adapter now captures MCP App resources for eligible failed results and marks their embedded results as errors. The web and mobile clients now check dynamic-tool items for MCP Apps regardless of status.

Changes: Failed MCP App results

Layer / File(s)Summary
Capture resources on failed results
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
The adapter captures MCP App resources for results without errors, including failed items. It marks attached results as errors for failed items. A replay test checks the resource URI, error flag, and pending-work state.
Extract MCP Apps in client timelines
apps/web/src/session-logic.ts, apps/mobile/src/lib/threadActivity.ts, apps/web/src/session-logic.test.ts
The web and mobile clients attempt MCP App extraction for dynamic-tool items regardless of status. A web test checks the MCP App entry for a failed GitHub tool call.

Priority: External image Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related PRs

Suggested reviewers: juliusmarminge, maria-rcks

Merge Risk: External image Minimal · up to 7623a

This change makes MCP App forms appear in Codex threads when a write tool defers to them, including when the tool call is reported as failed. I found no concrete defect in the changed paths. Reloading a page after submitting a form still shows the empty form, and the author says a stacked PR addresses that.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title is concise, uses the repository's conventional commit format, and clearly describes the main fix: MCP App forms now appear when Codex write tools defer to them.
Description checkPassedThe description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the bug, implementation, issue reference, focused tests, web and mobile validation, li…
Linked Issues checkPassedIssue #23825 requires a deferred MCP App result to remain visible in the thread. CodexAdapterV2.ts captures the app when a result exists, including a failed Codex result, and passes isError: true …
Out of Scope Changes checkPassedThe changes stay within issue #23825. The adapter preserves deferred MCP App results, the web and mobile code displays those results, and the added tests verify the behavior. No unrelated setting, wor…
Full details: Description check

Explanation

The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the bug, implementation, issue reference, focused tests, web and mobile validation, limitations, and the agent and harness used.

Full details: Linked Issues check

Explanation

Issue #23825 requires a deferred MCP App result to remain visible in the thread. CodexAdapterV2.ts captures the app when a result exists, including a failed Codex result, and passes isError: true to the app result. session-logic.ts and threadActivity.ts create MCP App entries for dynamic tools without requiring completed status. CodexAdapterV2.test.ts and session-logic.test.ts cover the failed deferred result. These changes satisfy the linked coding requirement.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue #23825. The adapter preserves deferred MCP App results, the web and mobile code displays those results, and the added tests verify the behavior. No unrelated setting, workflow, or feature change is present in the reviewed diff.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Added 2 commits

  • Merge remote-tracking branch 'origin/main' into mq-23850c4d269f
  • Merge remote-tracking branch 'origin/main' into HEAD7623a95