Skip to content

spec: Dynamic View Content via embedded resources#699

Draft
liady wants to merge 1 commit into
mainfrom
liady/dynamic-view-content-spec
Draft

spec: Dynamic View Content via embedded resources#699
liady wants to merge 1 commit into
mainfrom
liady/dynamic-view-content-spec

Conversation

@liady

@liady liady commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Note

Spec-only PR. The SDK implementation (types, helpers, worked example) is stacked on top as a separate draft PR (#700) so spec discussion isn't entangled with code review.

Motivation

See issue #169 .
A growing class of UI systems is generative: the server produces a declarative, typed UI description at tool-call time - a document, not code - and a generic runtime renders it. A2UI (application/a2ui+json) is the primary example, and there is active discussion about whether A2UI support should live inside the MCP Apps extension rather than as a parallel mechanism.

MCP Apps today has no good channel for these payloads:

  • structuredContent is untyped (no MIME type), single-valued, bound to the tool's outputSchema, and invisible to hosts that render the payload format natively.
  • Core MCP embedded resources already carry a uri, a mimeType, and text/blob content - but there is no standardized contract for how MCP Apps hosts treat them: hosts today may strip them from ui/notifications/tool-result or inject large presentation payloads into model context, and servers can't detect either.

This PR adds that contract to specification/draft/apps.mdx: Dynamic View Content - typed payloads returned from tool calls as marked embedded resources, forwarded by the host to the tool's predeclared renderer view.

The proposal

Zero new messages. Delivery rides entirely on the existing ui/notifications/tool-result notification and proxied tools/call responses. The proposal reduces to four primitives:

  1. Renderer declaration - contentMimeTypes?: string[] on UIResourceMeta: the view declares which payload MIME types it renders. Participates in the existing list-vs-read metadata precedence rules.
  2. Payload marking - _meta.ui.content (McpUiContentBlockMeta, with an optional rendererUri for future multi-view results) on embedded resource content blocks in CallToolResult.content.
  3. Host rules - marked blocks MUST be forwarded unmodified (in tool-result notifications and in proxied tools/call responses); SHOULD NOT be added to model context; MAY be type-filtered against the renderer's declared contentMimeTypes and size-limited.
  4. Capability negotiation - a contentMimeTypes extension setting; hosts MAY advertise ["*"] to forward any declared payload type opaquely (hosts never interpret payloads, only route them into the sandboxed view). Servers check it and degrade to text-only/structuredContent variants when absent.
sequenceDiagram
    autonumber
    actor User
    participant View as Renderer View (ui:// resource)
    participant Host as Host (AppBridge)
    participant Server as MCP Server

    Note over Host,Server: initialize - extensions["io.modelcontextprotocol/ui"]:<br/>{ mimeTypes: ["text/html#59;profile=mcp-app"], contentMimeTypes: ["application/a2ui+json"] }
    Host->>Server: resources/read ui://server/renderer
    Server-->>Host: HTML renderer, _meta.ui.contentMimeTypes: ["application/a2ui+json"]
    Note over Host: Renderer is predeclared, prefetchable,<br/>reviewable - unchanged from SEP-1865
    Host->>Server: tools/call search-flights (agent-initiated)
    Server-->>Host: CallToolResult: text fallback +<br/>embedded resource (application/a2ui+json, _meta.ui.content)
    Host->>View: ui/notifications/tool-result - marked blocks forwarded unmodified
    Note over Host: Marked payloads are NOT added to model context
    View->>View: extract marked payloads, render surface
    User->>View: clicks a button in the rendered surface
    View->>Host: tools/call select-flight (visibility: ["app"])
    Host->>Server: proxied tools/call
    Server-->>Host: new marked payload (incremental update)
    Host-->>View: tools/call response - marked blocks forwarded unmodified
    View->>View: apply update (interactive loop)
Loading

Relationship to SEP-1865's rejection of embedded resources

This deliberately does not revisit design decision #1 (Predeclared Resources vs. Inline Embedding): the renderer remains a predeclared, prefetchable, reviewable ui:// resource. Embedded resources here carry only data payloads consumed by that renderer - the same template/data split the spec already establishes with structuredContent, extended to typed, multi-valued, self-describing documents.

One server response, three host classes

The interop payoff of using core-MCP embedded resources (rather than an Apps-private channel) is that a server can serve a single tool response to every kind of host:

Host Negotiated Behavior
MCP Apps host mimeTypes: ["text/html;profile=mcp-app"], contentMimeTypes: ["application/a2ui+json"] Loads the ui:// renderer, forwards payloads to it
Native A2UI host mimeTypes: [..., "application/a2ui+json"] Renders marked payloads directly, skips the HTML renderer
Text-only host extension absent Renders the text content fallback

This directly addresses the "A2UI-over-MCP vs. A2UI-over-MCP-Apps" question: both are the same server response - the difference is only which capability the host advertises. A2UI-specific conventions (payload semantics, userAction / tool-call mapping) can layer on top of this generic mechanism as SDK/renderer guidance rather than spec text.

Design decisions (rejected alternatives in the Rationale section)

  • Explicit _meta.ui.content marker rather than MIME inference: servers legitimately return embedded resources for other purposes (files, records for the user); the marker makes routing intent unambiguous, and its rendererUri slot supports future multi-view tool results (already listed under SEP-1865's future considerations).
  • contentMimeTypes declared on the resource, not the tool: the renderer owns the parsing contract, multiple tools share it, and the declaration is reviewable at connection time alongside CSP. McpUiToolMeta stays untouched.
  • No dedicated ui/notifications/content message: it would duplicate delivery semantics, complicate ordering relative to tool-result, and grow host surface area for no expressive gain. Server-push content injection outside tool calls can be added later without conflicting with this design.
  • Payload URIs are ephemeral identifiers (per RFC 3986); servers are not required to serve them via resources/read, and ui:// remains reserved for renderable UI resources.

Security

Payloads are data, not code: they are interpreted by a renderer that is itself sandboxed, CSP-constrained, and reviewable under the existing model. The new Security section adds: renderers MUST treat payloads as untrusted input (no eval/innerHTML of payload-derived strings); payload-referenced origins remain subject to the renderer's declared CSP; payloads flow through auditable JSON-RPC with declared MIME types (hosts may log, size-limit, type-filter); and marked payloads are excluded from model context by default - strictly safer than today's ambiguity around embedded resources in results.

Backward compatibility

Fully additive. Hosts that don't advertise contentMimeTypes behave exactly as today; servers detect this and degrade. No existing messages, tools, or resources change. New reservations: the _meta.ui.content key on tool result content blocks, and the contentMimeTypes field in UIResourceMeta and in the extension settings.

Changes

One file: specification/draft/apps.mdx -

  • UIResourceMeta.contentMimeTypes + mirror in the resources/read response shape + the Metadata Location field list
  • Normative forwarding language on ui/notifications/tool-result
  • New Data Passing / 4. Dynamic View Content (via embedded resources) section (marker interface, requirements, host behavior, worked A2UI example) + a Best Practices bullet
  • contentMimeTypes extension setting under Capability Negotiation
  • Rationale: design decision Update mcp-ui PR #7 with rejected alternatives
  • Security: mitigation fix: use text/html+mcp mimeType format #6 (Dynamic Content Payloads)
  • Reservations: _meta.ui.content, contentMimeTypes

🤖 Generated with Claude Code

Adds standardized support for typed dynamic content payloads (e.g. A2UI,
application/a2ui+json) returned from tool calls as marked embedded
resources, delivered by the host to the tool's predeclared renderer View:

- contentMimeTypes renderer declaration on UIResourceMeta
- _meta.ui.content marker (McpUiContentBlockMeta) on embedded resource
  content blocks in tool results
- Normative host forwarding rules over ui/notifications/tool-result and
  proxied tools/call responses; payloads excluded from model context
- contentMimeTypes extension setting for capability negotiation, with
  a native-rendering path via the top-level mimeTypes array
- Rationale (decision #7), security mitigation (#6), and reservations

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/@modelcontextprotocol/ext-apps@699

@modelcontextprotocol/server-basic-preact

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-preact@699

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-react@699

@modelcontextprotocol/server-basic-solid

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-solid@699

@modelcontextprotocol/server-basic-svelte

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-svelte@699

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vanillajs@699

@modelcontextprotocol/server-basic-vue

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vue@699

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/@modelcontextprotocol/server-budget-allocator@699

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/@modelcontextprotocol/server-cohort-heatmap@699

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/@modelcontextprotocol/server-customer-segmentation@699

@modelcontextprotocol/server-debug

npm i https://pkg.pr.new/@modelcontextprotocol/server-debug@699

@modelcontextprotocol/server-lazy-auth

npm i https://pkg.pr.new/@modelcontextprotocol/server-lazy-auth@699

@modelcontextprotocol/server-map

npm i https://pkg.pr.new/@modelcontextprotocol/server-map@699

@modelcontextprotocol/server-pdf

npm i https://pkg.pr.new/@modelcontextprotocol/server-pdf@699

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/@modelcontextprotocol/server-scenario-modeler@699

@modelcontextprotocol/server-shadertoy

npm i https://pkg.pr.new/@modelcontextprotocol/server-shadertoy@699

@modelcontextprotocol/server-sheet-music

npm i https://pkg.pr.new/@modelcontextprotocol/server-sheet-music@699

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/@modelcontextprotocol/server-system-monitor@699

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/@modelcontextprotocol/server-threejs@699

@modelcontextprotocol/server-transcript

npm i https://pkg.pr.new/@modelcontextprotocol/server-transcript@699

@modelcontextprotocol/server-video-resource

npm i https://pkg.pr.new/@modelcontextprotocol/server-video-resource@699

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/@modelcontextprotocol/server-wiki-explorer@699

commit: 89ced6b

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant