fix(server): MCP App forms now show when a write tool defers to them#23850
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: trueon 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:
| 1 | claude-code ui extension: no |
| 2 | codex (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:
| 1 | tools/call create_note {"title":"Ship the fix"} -> isError, awaiting_user_submission |
| 2 | resources/read ui://notes/create-note.html |
| 3 | tools/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.
After: the form shows up with the title the agent passed in.
Clicking "Create note" asks for confirmation. After confirming, the server gets the _ui_submitted call and creates the note. Server log for the turn:
| 1 | tools/call create_note {"title":"Ship the fix"} -> isError, awaiting_user_submission |
| 2 | tools/call create_note {"title":"Ship the fix","_ui_submitted":true} -> Created note "Ship the fix". |
Tests
CodexAdapterV2.test.ts: a failed call with a result still gets its app captured, and the app seesisError: 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.
After: GitHub's "Create issue" form shows in the thread.
Clicking "Create issue" asks for confirmation with the _ui_submitted: true call, and after confirming GitHub created the issue.
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.
| Form | Submitted | Mobile line 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
Added 1 commit
- fix(server): show MCP app forms when a write tool defers to them335e7e2
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.
No actionable comments were generated in the recent review.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: main
- File: .coderabbit.config.ts
- Commit: 5211acf
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Advanced
- Run ID:
c4ee1bbb-7a1f-42ff-9ecb-9272b2adb001
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.tsapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.tsapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.tsapps/web/src/session-logic.test.tsapps/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.tsThe 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.tsThe 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
- pingdotgg/t3code#18764: Both PRs change Codex adapter handling of MCP App captures for failed work.
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 name Status Explanation Title check Passed The 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 check Passed 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, li… Linked Issues check Passed Issue #23825requires a deferred MCP App result to remain visible in the thread.CodexAdapterV2.tscaptures the app when a result exists, including a failed Codex result, and passesisError: true…Out of Scope Changes check Passed 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, 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
#23825requires a deferred MCP App result to remain visible in the thread.CodexAdapterV2.tscaptures the app when a result exists, including a failed Codex result, and passesisError: trueto the app result.session-logic.tsandthreadActivity.tscreate MCP App entries for dynamic tools without requiringcompletedstatus.CodexAdapterV2.test.tsandsession-logic.test.tscover 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.