Skip to content

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

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

CouchRiv wants to merge 2 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. One mcpServerStatus() read per Claude process serves every later call; listTools refreshes it, an unknown tool triggers one re-read, and a failed or unhelpful read waits 60 s before the next. Non-app MCP tools pay no extra request once the catalog is loaded.
  • Result. result is Claude Code's tool_use_result for MCP Apps tools (content, structuredContent, _meta), 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.
  • 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. ClaudeAgentSdkQueryRunner.test.ts checks the CLI env. vp run check:changed passes (format, lint, knip, typecheck, 3 test files).
  • 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, through T3 Code. Reviewed by GPT-6.1 Sol (Codex), Grok 4.7 and Claude Opus 5.5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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
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.

Written by Claude Opus 5.5 in Claude Code, through T3 Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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 →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 3fd2b85 and 492d197.

Files ignored due to path filters: 1
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing: 7
  • apps/server/package.json
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAgentSdkQueryRunner.test.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeMcpApps.ts
  • docs/user/html-renders.md
  • pnpm-workspace.yaml

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 Claude adapter now supports MCP Apps tool listing, calls, and resource reads. It can capture eligible app resources into attachments and associate them with completed tool rows. The changes also update the Claude Agent SDK version and document Claude MCP Apps behavior.

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
The query session exposes MCP server status, resource reads, and tool calls. Claude MCP Apps support is enabled when the CLI advertises both required capabilities. The SDK version and its workspace exclusion are updated.
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, docs/user/html-renders.md
Claude MCP tools map to app targets when their names are unique and their metadata contains a valid resource URI. The runtime lists tools, reads resources, and calls tools. The documentation describes Claude support and its stated exceptions.
App capture and tool-row settlement
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Eligible completed 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.

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Claude CLI
  participant ClaudeAdapterV2
  participant ClaudeAgentSdkQuerySession
  participant MCP resource
  participant Tool row
  Claude CLI->>ClaudeAdapterV2: Complete an eligible MCP tool call
  ClaudeAdapterV2->>ClaudeAgentSdkQuerySession: Read the app resource
  ClaudeAgentSdkQuerySession-->>ClaudeAdapterV2: Return resource contents
  ClaudeAdapterV2->>Tool row: Attach snapshot and normalized result
Loading

Suggested reviewers: juliusmarminge

Merge Risk: Minimal · up to 492d1

Claude threads can now render MCP Apps, with a consent prompt before each app tool call and a fallback to ordinary tool rows when capture fails. No merge-blocking issue was found. The one thing to keep in mind is that callTool relies on an SDK request method that is not yet public, so a future SDK upgrade could break it.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description includes all required sections and provides detailed problem, implementation, verification, limitations, and scope information. However, the Scope and approval section explicitly state… Obtain and link explicit maintainer approval for the direction and scope, including approval for the request-channel mcp_call implementation and reliance on CLAUDE_CODE_MCP_APPS_HOST, or explain why the change qualifies for the small-fix ex…
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title is concise, specific, and accurately 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 all required sections and provides detailed problem, implementation, verification, limitations, and scope information. However, the Scope and approval section explicitly states that no maintainer has approved the direction, despite the template requiring explicit maintainer approval or an explanation for a small focused fix.

Resolution

Obtain and link explicit maintainer approval for the direction and scope, including approval for the request-channel mcp_call implementation and reliance on CLAUDE_CODE_MCP_APPS_HOST, or explain why the change qualifies for the small-fix exception.

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

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.

1 participant