Skip to content

MCP Apps (SEP-1865) client support: tool-result _meta, resource mimeType/_meta, and capabilities.extensions are dropped #6613

Description

@PhilipAD

Description

MCP Apps (SEP-1865, extension id io.modelcontextprotocol/ui) reached Final and ships as an official extension in the MCP 2026-07-28 release candidate. Hosts including ChatGPT, Claude, Goose, and VS Code have shipped support, and servers are appearing from Shopify, Hugging Face, and ElevenLabs.

pydantic-ai currently has no client-side support for it. Searching at fa0ec68:

grep -rIn "io\.modelcontextprotocol" .   -> 0 hits
grep -rIn "ui://" .                       -> 0 hits
grep -rIn "SEP-1865\|mcp-app\|mcp-ui" .   -> 0 hits

I could not find an existing issue for this (MCP Apps, mcp-ui, SEP-1865, ui extension, apps extension all return nothing relevant). #2616 is adjacent but covers the outbound direction — metadata sent to the server via process_tool_call — rather than UI metadata coming back.

The problem I'm trying to solve. I'm building a system where a headless pydantic-ai agent process holds the MCP connection and a separate browser front-end renders the UI. SEP-1865 defines the host by its message-handling obligations rather than by process identity — the spec explicitly models hosts as proxies ("Hosts act as MCP servers (that can proxy the actual MCP server)") and already mandates a multi-hop View ↔ Sandbox ↔ Host path across different origins. So a split host is spec-legal, and the relay itself is mine to build. I'm not asking pydantic-ai to render anything.

What blocks me is that MCP Apps metadata is discarded before it reaches me, so I cannot build the relay on top of pydantic-ai without patching it.

What's blocking

Three specific places, in rough priority order.

1. Tool-result _meta is dropped. _map_mcp_tool_result (pydantic_ai_slim/pydantic_ai/mcp.py:1527) discards _meta on tool results. The existing comment already anticipates this:

Tool results don't preserve MCP annotations/_meta onto BinaryContent.vendor_metadata; only _map_mcp_prompt_part does that via _map_mcp_binary_content. The PR that added prompts made this asymmetric on purpose (tool returns flow to the model; prompt content flows to the user). Revisit if a future PR decides tool returns should also surface MCP annotations.

I want to flag that the stated rationale — "tool returns flow to the model" — is exactly what MCP Apps changes. Under SEP-1865 a tool return also flows to a view, and per-result UI state rides in that _meta. _mcp_part_metadata already exists at mcp.py:1554; it's simply not called from the tool path. This is the hardest blocker: without it there is no way to receive per-call UI state.

2. read_resource() drops mimeType and resource _meta. mcp.py:1376-1387 returns bare str/BinaryContent via _resource_content_to_pai. For a ui:// resource, mimeType (text/html;profile=mcp-app) is how you identify it as an app at all, and _meta.ui carries the CSP and permissions needed to construct the sandbox. Both are lost.

3. capabilities.extensions is dropped, and never advertised. ServerCapabilities.from_mcp_sdk() (mcp.py:532-585) hand-copies a fixed field list and drops extensions. It survives in the SDK model via extra="allow", so it's reachable at toolset.client.initialize_result.capabilities.model_extra["extensions"], but not through the public API. On the outbound side pydantic-ai never constructs ClientCapabilities, so it cannot advertise io.modelcontextprotocol/ui — meaning a compliant server correctly concludes there is no UI and never offers apps. Per the spec, "Servers SHOULD check client capabilities before registering UI-enabled tools."

Worth noting these are all pydantic-ai-layer issues rather than SDK ones, and one thing already works: tool-definition _meta is preserved (mcp.py:1144), so discovering which tools are app-backed is already possible today.

Dependency context

  • pydantic-ai pins fastmcp-slim[client]>=3.3.0; uv.lock resolves fastmcp 3.4.2 and mcp SDK 1.28.1.
  • FastMCP 3.4.2 already implements MCP Apps server-sidefastmcp/apps/ defines UI_EXTENSION_ID = "io.modelcontextprotocol/ui" plus typed ResourceCSP (connectDomains/resourceDomains/frameDomains/baseUriDomains) and ResourcePermissions models, and fastmcp/server/low_level.py:217-232 advertises the extension.
  • The FastMCP client has none of it (grep -rIn "UI_EXTENSION_ID\|io\.modelcontextprotocol/ui" fastmcp/client/ → nothing).
  • MCP SDK 1.28.1 has no extensions field on ClientCapabilities/ServerCapabilities; FastMCP works around this with model_copy, exploiting extra="allow".

So the server half exists in the dependency tree with reusable typed models for CSP/permissions. That may make the client half a smaller change than it first appears.

What I'm asking for

Not a design — alignment on whether this belongs in core, and if so what shape you'd want. Concretely I think items 1-3 above are core, since they're metadata-preservation in the MCP client itself and are useful beyond MCP Apps to anyone reading MCP annotations off tool results. The relay, iframe hosting, CSP enforcement, and sandbox origin isolation are all clearly mine and I'd expect to build them downstream or as a pydantic-ai-* package.

I'd also understand if you consider item 1's asymmetry worth preserving and would rather expose result _meta through a separate opt-in path than change the default mapping — that would work for my use case too.

Champion context

Per CONTRIBUTING: I have a concrete production use case (the split host above), I've traced the specific blockers to line level, and I'm happy to validate fixes against a real MCP Apps server and report back. I can also contribute the spec-conformance detail — I recently diffed a gateway implementation against the 2026-01-26 spec and have the normative requirements mapped, including the parts most implementations get wrong (the ;profile=mcp-app MIME parameter surviving middleware, and the deprecated-vs-nested resourceUri key forms).

Happy to write the code if you'd prefer, but per CONTRIBUTING I'm not opening a PR before a maintainer agrees on the approach.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    MCPfeatureNew feature request, or PR implementing a feature (enhancement)needs-discussionp:3-midSignal 4-6: normal backlog.pydanty:featureManaged by pydanty dogfooding automationtriage:legitimate

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions