Skip to content

feat(server): render MCP apps in Claude threads - #28739

Open
CouchRiv wants to merge 4 commits into
pingdotgg:mainfrom
CouchRiv:feat/mcp-apps-claude
Open

CouchRiv wants to merge 4 commits into
pingdotgg:mainfrom
CouchRiv:feat/mcp-apps-claude

Conversation

@CouchRiv

@CouchRiv CouchRiv commented Nov 1, 2026 •

Copy link
Copy Markdown

Problem

MCP apps render only in Codex threads. In a Claude thread, a tool whose server declares an MCP App (_meta.ui.resourceUri) shows as an ordinary tool row, and an app view cannot reach Claude's MCP connections (McpAppRequests answers provider-unsupported). #16236 left Claude out because its SDK had no resource read. It has one now: Query.readMcpResource() and the tools' _meta.ui in mcpServerStatus() shipped in @anthropic-ai/claude-agent-sdk 0.3.280.

Change

Claude Code owns the MCP connections, so ClaudeAdapterV2 implements the same capture and the same three mcpApps operations as CodexAdapterV2, through the live query:

  • Host announcement. The CLI is launched with CLAUDE_CODE_MCP_APPS_HOST=1, which makes Claude Code announce io.modelcontextprotocol/ui to its MCP servers (Claude's counterpart of CODEX_CLIENT_CAPABILITIES). A user env value still wins.
  • Capture. When an MCP tool settles in the root thread and the CLI advertises mcp_tool_ui_meta_v1 and mcp_read_resource_v1, the adapter looks the tool up in a per-process catalog built from mcpServerStatus() (prefetched at init; a tool the cached catalog does not know is looked up by the capture, never on the message reader), reads its ui:// page with readMcpResource(), stores it with snapshotMcpApp, and emits the row once as server.tool with output: { t3McpApp, result }. As in Codex, the capture runs off the reader and the row stays running until it lands, which keeps the run open past the turn's end. An interrupted, failed or cancelled turn, or the Claude process exiting, shows the plain row instead; a capture that loses that race discards its stored page. Any failure falls back to the plain row.
  • Catalog. A per-process catalog read from mcpServerStatus() serves later calls for up to 60 s; the next MCP tool call after that refreshes it off the reader, with one shared read. listTools also refreshes it. A tool the catalog does not know triggers one re-read, then waits 60 s before the next re-read for that tool only, and a failed read waits 60 s. A call that failed without Claude Code's own tool result (a transport error, such as a disconnected server) stays a plain row and reads nothing, as in Codex.
  • Result. result is Claude Code's tool_use_result for MCP Apps tools (content, structuredContent and _meta, each kept when present), else the model-facing content, with isError for a failed call.
  • App requests. listTools and readResource use the public SDK methods. callTool sends the mcp_call control request with result_not_for_model. The SDK types this request but has no public method for it yet (Typed SDK surface for out-of-band MCP tool calls / resources (MCP Apps support) anthropics/claude-agent-sdk-typescript#380), so it goes through the query's request channel in one place, and replies from CLIs without result_not_for_model are converted. A tool whose native mcp__server__tool name is shared by another configured tool after Claude Code's normalization is refused. When the thread's Claude process is not running (after a stop, or once it exits), app requests fail with session-stopped, as in a Codex thread whose session stopped: the adapter tags that failure with a new fixed reason, session_not_running, which McpAppRequests maps.
  • Consent. mcp_call runs outside Claude Code's permission pipeline, so tool annotations are not forwarded to apps and T3 asks before every app tool call in Claude threads.
  • Bumps @anthropic-ai/claude-agent-sdk from 0.3.276 to 0.3.316 (0.3.280 is the minimum for these APIs).

The env and callTool ship together on purpose: announcing the extension without working app tool calls would turn writes that servers defer to an app form (#23850's GitHub case) into forms that cannot submit.

Not covered, and stated in the user guide: apps called by Claude subagents (their rows have no run, so the app cannot reach the live query) and ui/update-model-context notes, which only Codex consumes today. Also not covered: a model change while a page is loading shows the plain row (blocking the change would surface ClaudeBackgroundWorkBlocksQueryReplacementError instead), and a user frame that carries several tool results keeps the model-facing content only, because Claude Code sends tool_use_result per message.

Scope and approval

This extends the established MCP Apps capability to a second provider through the adapter contract #16236 built for it ("adding a provider means implementing three adapter methods", "through the provider's own MCP client"). The same direction was proposed in Ideas discussion #21859 (by broomva, with a prototype on an older main); this PR follows that design on current main. No maintainer has approved the direction yet. Two points need a maintainer decision: sending mcp_call through the request channel until #380 lands, and relying on the undocumented CLAUDE_CODE_MCP_APPS_HOST.

Verification

  • Focused tests in ClaudeAdapterV2.test.ts (wake harness): an app replaces its row, Claude's messages keep flowing while the catalog loads (this test hangs without the fix), the row stays running past turn.terminal until the page lands, the result keeps structuredContent from the recorded tool_use_result shape, failed calls keep the app with isError; plain rows for a tool without an app, an unreadable page, an unreadable catalog and a CLI without the capabilities (no mcp_status call); the plain row when the turn is interrupted or Claude exits mid-capture; listTools, readResource and callTool through the live query, including the ambiguous-name refusal; the catalog expiring (a tool that gains an app, a changed ui:// URI, a miss on one tool not holding another), a tool_use_result without content keeping structuredContent and _meta, a transport failure reading nothing, and session_not_running once the query has ended. Each of these last cases fails without its fix. McpAppRequests.test.ts: a stopped Claude session reports session-stopped, other protocol errors stay request-failed. ClaudeAgentSdkQueryRunner.test.ts checks the CLI env. vp run check:changed passes (format, lint, knip, typecheck, focused tests).
  • Real run (on an earlier revision of this branch, repeated on the reviewed revision before the last catalog fixes): dev server from this branch, Claude Code 2.1.316, Claude Haiku 5.5, a local stdio MCP server with MCP Apps. Before (upstream main): the call is a plain row with raw output. After: the card renders in place of the row; the stored item is visionneuse.bc_view with t3McpApp and the page attachment. An MCP Apps lab card made app-initiated lab_report calls; each one went through T3's approval dialog and reached Claude Code as mcp_call (provider log query.mcp_call). Screenshots (before, after, app tool call approval, app tool call result) follow in a comment.
  • Not checked: mobile, remote and relay connections, older Claude Code releases, a server that defers writes to a form (GitHub MCP).

Written by Claude Opus 5.5 in Claude Code, and the catalog and stopped-session follow-ups by GPT-6.1 Sol in Codex, through T3 Code. Reviewed by GPT-6.1 Sol (Codex), Grok 4.7, DeepSeek V4.1 Flash and Claude Opus 5.5.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Nov 1, 2026
@CouchRiv

CouchRiv commented Nov 1, 2026

Copy link
Copy Markdown
Author

Screenshots from the real run described in Verification (dev server from this branch, Claude Code 2.1.316, Claude Haiku 5.5, local stdio MCP server with MCP Apps).

Before (upstream main): the app tool call is a plain tool row.

before

After (this branch): the app renders in place of the row.

after

App-initiated tool call: T3 asks before lab_report runs in a Claude thread.

approval

Result: the app received the call's result (event log inside the app).

result

@CouchRiv
CouchRiv marked this pull request as ready for review November 1, 2026 19:47
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Files ignored due to path filters: 1
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Run configuration
Commits

Reviewing files that changed from the base of the PR and between c6ab437 and 67f725b.

Files ignored due to path filters: 1
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

The Claude adapter now supports MCP Apps tool listing, calls, and resource reads. It can capture eligible app resources as attachments associated with tool rows. The changes also update provider-session failure mapping, the Claude Agent SDK version, and MCP Apps documentation.

Changes: Claude MCP Apps

Layer / File(s) Summary
SDK operations and capability wiring
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAgentSdkQueryRunner.test.ts, apps/server/package.json, pnpm-workspace.yaml, docs/user/html-renders.md
The query session exposes MCP server status, resource reads, and tool calls. The adapter enables MCP Apps when the CLI reports both required capabilities. The SDK version and workspace exclusion are updated, and the documentation describes Claude support and its stated conditions.
Tool mapping and MCP Apps runtime
apps/server/src/orchestration-v2/Adapters/ClaudeMcpApps.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Claude MCP tools map to app targets when normalized names are unique and metadata contains a valid resource URI. The runtime lists tools, reads resources, calls tools, and converts results.
App capture and tool-row settlement
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Eligible tool calls can read and snapshot an app resource before the tool row settles. Capture failures, interruptions, and query exits settle the row as a plain MCP row.

MCP session failure mapping

Layer / File(s) Summary
Provider failure reason and request mapping
packages/provider-core/src/server/ProviderAdapter.ts, packages/provider-core/src/server/failure.ts, apps/server/src/mcpApps/McpAppRequests.ts, apps/server/src/mcpApps/McpAppRequests.test.ts
The provider failure contract adds session_not_running. MCP tool listing, tool calls, and resource reads map that reason to session-stopped; other adapter errors map to request-failed and preserve the cause.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant ClaudeCLI
  participant ClaudeAdapterV2
  participant QuerySession
  participant ToolRow
  ClaudeCLI->>ClaudeAdapterV2: Complete eligible MCP tool call
  ClaudeAdapterV2->>QuerySession: Read app resource
  QuerySession-->>ClaudeAdapterV2: Return resource contents
  ClaudeAdapterV2->>ToolRow: Attach snapshot and MCP result
Loading

Possibly related issues

  • pingdotgg/t3code#27552: The SDK upgrade uses version 0.3.316, above the version identified in the issue as fixing compacted fork metadata remapping.

Suggested reviewers: juliusmarminge

Merge Risk: Minimal · previous review c6ab4

No actionable merge-blocking issue is established; the change is mergeable after normal checks.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description includes the required Problem, Change, Scope and approval, and Verification sections. It provides detailed behavior, limitations, tests, and manual verification. However, the Scope and… Obtain and document explicit maintainer approval for the scope and direction, including the use of the request channel for mcp_call and reliance on CLAUDE_CODE_MCP_APPS_HOST. Add the approval comment or discussion link to the Scope and appr…
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: rendering MCP apps in Claude threads.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes the required Problem, Change, Scope and approval, and Verification sections. It provides detailed behavior, limitations, tests, and manual verification. However, the Scope and approval section states that no maintainer has approved the direction and identifies unresolved decisions about the request-channel approach and undocumented environment variable.

Resolution

Obtain and document explicit maintainer approval for the scope and direction, including the use of the request channel for mcp_call and reliance on CLAUDE_CODE_MCP_APPS_HOST. Add the approval comment or discussion link to the Scope and approval section.

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

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between b4659ec and c6ab437.

Files selected for processing: 7
  • apps/server/src/mcpApps/McpAppRequests.test.ts
  • apps/server/src/mcpApps/McpAppRequests.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeMcpApps.ts
  • packages/provider-core/src/server/ProviderAdapter.ts
  • packages/provider-core/src/server/failure.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.

@CouchRiv
CouchRiv force-pushed the feat/mcp-apps-claude branch 2 times, most recently from 9dd3cbf to 5643d3f Compare November 2, 2026 17:15
@maria-rcks

maria-rcks commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified head 5643d3f3d4a769d7f01c1105936b84448bdbc950 as-is on linux, using repo Playwright and /usr/bin/google-chrome. installed with vp i and ran one background vp run dev at a time, with isolated empty state. projects, threads and turns were created through the web app. no mocks, fixtures or replay provider.

  • reproduced on github/main (f6503f0e717f): claude haiku 5.5 called read_me and create_view on the live https://mcp.excalidraw.com/mcp server, but showed a plain tool row.
  • repeated the same prompt on this head with claude code 2.1.316 and real haiku 5.5 through the local proxy: the diagram rendered inline. provider logs show query.read_mcp_resource; fullscreen editing and returning inline worked.
  • app export asked for approval. cancelling sent no mcp_call; confirming reached query.mcp_call and returned an excalidraw URL. drawing a rectangle triggered save_checkpoint approval. after confirming, a read from the live server showed that rectangle persisted. a subsequent claude message returned followup-ready normally.
  • nearby paths: ordinary read_me calls completed, and codex gpt-6.1-sol rendered an MCP diagram through the same web client.
  • blacksmith passed the focused adapter/query-runner checks (309 tests) and vp run check:changed against this PR's base (310 tests, format, lint, knip, graphql and typechecks). no source changes.

before, same diagram request on main:

main shows create_view as a plain tool row

after, fresh claude turn on this head:

pr renders the client and provider diagram inline

fullscreen, return inline, app export approval and cancellation:

claude mcp app interaction and approval

runtime transcript. inspected the images and recording; uploaded video playback and seeking passed.

Not verified

native desktop, ios/android, remote/relay/tunnel connections, older claude releases, subagents, model changes during capture, and external write forms beyond excalidraw were not exercised.

two limits observed: one cold inline view stayed blank until entering and leaving fullscreen; a fresh claude turn rendered inline without that workaround, so its cause remains unverified. after a successful export call, the later link-open was blocked by the host's existing user-activation rule, so opening the exported page was not verified. these checks prove app capture, rendering and provider tool calls, not every app interaction.

An MCP tool missing from the cached catalog made the tool-result handler
re-read Claude Code's MCP status while holding the SDK message lock, so a
slow server could stall streamed text and other tools for up to the lookup
timeout. Codex captures off its event reader for the same reason.

The reader now only checks the cached catalog. A tool it does not know is
looked up by the capture fiber, which already keeps the row running until it
lands and falls back to the plain row.
Claude MCP app rows could lose structured results without native content,
keep stale app metadata forever, and pause all unknown tools after one miss.
Transport failures also triggered app lookups while servers were disconnected.

Preserve result metadata independently of content, expire catalogs after
60 seconds with shared refreshes, track misses per tool, and skip app capture
for errors without Claude Code's tool_use_result.

Tests: 16 focused MCP app cases pass; all five new regression cases fail
without the production fixes (verif-repro: fail->pass). check:changed passes.

Written by GPT-6.1 Sol in Codex, through T3 Code.
MCP apps received request-failed when a Claude CLI query had stopped or
belonged to another native thread. Apps could not recognize that they needed
to send a message to restart their thread.

Add the fixed session_not_running provider reason and return it from Claude's
live-query guard. Map only that protocol failure to session-stopped for app
tool lookups, tool calls, and resource reads, preserving all other failures.

Tests: 26 focused app and Claude adapter tests pass, including stopped-session
and unreasoned-protocol-error cases. verif-repro proves fail->pass without the
production fixes. vp run check:changed passes.

Written by GPT-6.1 Sol in Codex, through T3 Code.
@CouchRiv
CouchRiv force-pushed the feat/mcp-apps-claude branch from c6ab437 to 67f725b Compare November 3, 2026 17:46

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants