Skip to content

feat: Dynamic View Content SDK support (types, helpers, example)#700

Draft
liady wants to merge 4 commits into
liady/dynamic-view-content-specfrom
liady/dynamic-view-content-sdk
Draft

feat: Dynamic View Content SDK support (types, helpers, example)#700
liady wants to merge 4 commits into
liady/dynamic-view-content-specfrom
liady/dynamic-view-content-sdk

Conversation

@liady

@liady liady commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Note

Stacked on #699 (the spec change) - this PR's base is liady/dynamic-view-content-spec, so the diff here shows only the SDK implementation. Retarget to main once #699 merges.

SDK implementation of Dynamic View Content: typed presentation payloads carried in tool results as embedded resource content blocks marked with _meta.ui.content, forwarded by hosts to the tool's predeclared renderer view.

Three commits, layered:

1. Types + schemas (src/spec.types.ts, regenerated src/generated/)

  • McpUiResourceMeta.contentMimeTypes?: string[] - the renderer declaration
  • McpUiContentBlockMeta - the _meta.ui.content marker interface (optional rendererUri for future multi-view results)
  • McpUiClientCapabilities.contentMimeTypes?: string[] - the extension setting (supports the ["*"] opaque-forwarding wildcard)
  • Zod schemas regenerated from the spec types; re-exports through src/types.ts

Notably, App and AppBridge need no behavioral changes: both already transport CallToolResult opaquely, so the payload channel exists - the spec constrains hosts from filtering it and keeps payloads out of model context.

2. Helpers (src/ui-content.ts, re-exported from the app, app-bridge, and server entry points)

  • createViewContentBlock() - server-side: builds a correctly-marked embedded resource block; validates text/blob exclusivity and rejects ui:// payload URIs (reserved for renderable UI resources)
  • getViewContentBlocks() / isViewContentBlock() - view-side: extracts marked payloads from tool results in array order, with mimeType/rendererUri filters; works on both ontoolresult params and callServerTool() results
  • supportsContentMimeType() - capability check honoring the ["*"] wildcard, for servers gating renderer-pattern tools and hosts type-filtering payloads

With unit tests and type-checked JSDoc examples (ui-content.examples.ts companions, synced via sync:snippets).

3. Worked example (examples/dynamic-content-server)

A generic renderer view with no domain-specific presentation logic, driven end-to-end by declarative payloads:

  • The renderer resource declares contentMimeTypes in its _meta.ui
  • search-flights (model-visible) returns a text fallback for model context plus a marked payload built with createViewContentBlock()
  • select-flight (visibility: ["app"], hidden from the model) is called by the renderer's event bridge - payload-level button actions become tools/call requests, and responses carry the next payload, closing the interactive loop
  • Payloads are treated as untrusted input: DOM is built with createElement/textContent only, never innerHTML of payload-derived strings

The example uses a deliberately tiny stand-in format (application/vnd.example.dynamic-ui+json, defined in dynamic-ui.ts) so the plumbing stays easy to follow - the pattern is identical for real generative UI formats like application/a2ui+json.

Dynamic content renderer showing payload-driven flight cards inside the sandboxed view

Testing

  • 14 new unit tests for the helpers + regenerated schema round-trip tests (389 total passing)
  • E2E: the example is wired into tests/e2e/servers.spec.ts, exercising the full pipeline (server → marked embedded resource → host forwards via sendToolResult → renderer extracts and renders). Golden screenshot generated and verified in the CI-identical Linux Playwright container.
  • typedoc --treatValidationWarningsAsErrors clean

Also includes a small basic-host fix discovered while testing: res.sendFile with an absolute path applies express's dotfiles: "ignore" rule to every path segment, 404ing sandbox.html in any checkout under a dot-directory (e.g. git worktrees under .claude/worktrees); passing the file relative to a root fixes it. package-lock.json picks up the new workspace entry plus an npm dedup of nested @types/node/undici-types entries.

Follow-ups (out of scope here)

  • basic-host advertising contentMimeTypes in its client capabilities, to model the negotiation as a reference host (forwarding already works unmodified today)
  • Adding the example to the README grid (grid-cell.png + table placement)
  • Optional A2UI-specific renderer/SDK guidance (payload semantics, userAction ↔ tool-call mapping conventions) layered on this generic mechanism

🤖 Generated with Claude Code

liady and others added 3 commits July 8, 2026 02:11
- McpUiResourceMeta.contentMimeTypes: renderer declaration for typed
  dynamic content payloads
- McpUiContentBlockMeta: the _meta.ui.content marker for embedded
  resource blocks in tool results
- McpUiClientCapabilities.contentMimeTypes: capability negotiation
  setting (supports ["*"] opaque forwarding)
- Regenerated Zod schemas and re-exports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New src/ui-content.ts module, re-exported from the app, app-bridge, and
server entry points:

- createViewContentBlock(): server-side helper producing an embedded
  resource block marked with _meta.ui.content (validates text/blob
  exclusivity and rejects ui:// payload URIs)
- getViewContentBlocks() / isViewContentBlock(): view-side extraction of
  marked payloads from tool results, with mimeType/rendererUri filters
- supportsContentMimeType(): capability check honoring the ["*"]
  wildcard, for servers gating renderer-pattern tools and hosts
  type-filtering payloads

Includes type-checked JSDoc examples (ui-content.examples.ts) and unit
tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A full worked example of Dynamic View Content:

- Generic renderer view (no domain-specific presentation logic) that
  declares contentMimeTypes, extracts marked payloads from tool results
  with getViewContentBlocks(), and renders them via createElement/
  textContent only (payloads are untrusted input)
- search-flights tool returning a text fallback plus a declarative UI
  payload built with createViewContentBlock()
- select-flight app-visibility tool driven by the renderer's event
  bridge, whose responses carry new payloads (the interactive loop)
- E2E coverage with a golden screenshot generated in the CI-identical
  Playwright container

Also fixes basic-host sandbox.html serving under dot-directory checkouts
(e.g. git worktrees in .claude/worktrees): res.sendFile with an absolute
path applies the dotfiles=ignore rule to every path segment and 404s;
pass the file relative to a root instead.

package-lock.json: new workspace entry, plus npm dedup of nested
@types/node/undici-types entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous lockfile update was generated with a newer npm that deduped
the nested examples/*/node_modules/@types/node and undici-types entries,
which npm 10 (Node 22, used in CI) still resolves as required - so npm ci
failed with 'lock file out of sync'. Regenerated from main's lockfile via
npm install --package-lock-only in a linux/amd64 node:22 container; the
diff vs main is now purely additive (the dynamic-content-server workspace
entries only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

📖 Docs Preview Deployed

Preview (stable) https://pr-700.mcp-ext-apps-docs-preview.pages.dev
This commit https://f4393655.mcp-ext-apps-docs-preview.pages.dev
Commit 269fa40

Includes drafts and future-dated posts. All pages served with noindex, nofollow — search engines will not crawl this preview.

@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@700

@modelcontextprotocol/server-basic-preact

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

@modelcontextprotocol/server-basic-react

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

@modelcontextprotocol/server-basic-solid

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

@modelcontextprotocol/server-basic-svelte

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

@modelcontextprotocol/server-basic-vanillajs

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

@modelcontextprotocol/server-basic-vue

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

@modelcontextprotocol/server-budget-allocator

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

@modelcontextprotocol/server-cohort-heatmap

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

@modelcontextprotocol/server-customer-segmentation

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

@modelcontextprotocol/server-debug

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

@modelcontextprotocol/server-lazy-auth

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

@modelcontextprotocol/server-map

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

@modelcontextprotocol/server-pdf

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

@modelcontextprotocol/server-scenario-modeler

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

@modelcontextprotocol/server-shadertoy

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

@modelcontextprotocol/server-sheet-music

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

@modelcontextprotocol/server-system-monitor

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

@modelcontextprotocol/server-threejs

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

@modelcontextprotocol/server-transcript

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

@modelcontextprotocol/server-video-resource

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

@modelcontextprotocol/server-wiki-explorer

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

commit: 2cdc509

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