* feat(server): add hasUiSupport/getUiCapability for experimental+extensions
Support double-tagging for MCP Apps capability negotiation:
- Check both experimental and extensions fields in client capabilities
- Add hasUiSupport() to easily check if client supports MCP Apps
- Add getUiCapability() to retrieve the capability settings
- Update spec to document both capability locations
This enables forward compatibility as MCP transitions from experimental
to the extensions field (SEP-1724).
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
* fix: update @types/node version in package-lock.json
Replace invalid 22.19.5/22.19.6/22.19.7 versions with valid 22.19.3
* docs(spec): clarify hosts SHOULD double-tag, servers SHOULD check both
- Hosts SHOULD advertise capabilities in both experimental and extensions
- Servers SHOULD check both locations, preferring extensions when present
- Updated example to show double-tagging pattern
* chore: remove double-tagging recommendation, keep hasUiSupport helper
Reverts the spec changes that recommended clients double-tag capabilities
in both experimental and extensions fields. The helper functions remain
as they're useful for checking capability in either location.
* refactor(server): simplify to only getUiCapability, remove hasUiSupport
- Remove hasUiSupport function (use getUiCapability directly)
- Remove double-tagging logic (only check extensions field)
- Add ClientCapabilitiesWithExtensions type using SDK's ClientCapabilities
- Update spec to use getUiCapability
- Simplify tests
* fix: update @see link to getUiCapability
* refactor(server): inline ClientCapabilitiesWithExtensions type in getUiCapability
* refactor: move McpUiClientCapabilities to spec.types.ts
* Update spec: drop Intents reference, rename Notify to Log
- Remove "intent" from the list of MCP-UI message types in the rationale
section (Intents concept is being dropped)
- Rename "Notify" to "Log" in the lifecycle diagram to better reflect
that notifications/message is for debugging/telemetry logging, not
for sending messages to the chat (which is now ui/message)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* spec: remove Intents reference from rationale
Remove 'intent' from the list of MCP-UI message types - Intents concept was dropped in earlier discussions.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* docs: standardize UI terminology to use "view" consistently
Replaces "widget" and "Guest UI" terminology with "view" across
the codebase as decided in the community survey.
This change updates:
- Specification document
- SDK source code and JSDoc comments
- Example servers and documentation
- Plugin documentation and skills
Closes#71
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update the spec to clarify that:
- ui.domain format and validation rules are host-dependent
- Servers must consult host-specific documentation for expected format
- Common patterns: hash-based subdomains, URL-derived subdomains
- Updated examples to reflect realistic host-controlled domains
This addresses the gap between the spec's previous implied flexibility
(arbitrary domains like weather-widget.example.com) and real-world
constraints where hosts must control sandbox domains for security.
The spec document incorrectly defined permission fields (camera, microphone,
geolocation, clipboardWrite) as boolean types, but the SDK implementation
uses empty object types ({}).
This caused validation errors for MCP servers (like Hex) that followed
the spec and sent boolean values like `clipboardWrite: true`, when the
Zod schema expects `clipboardWrite: {}`.
The empty object design was intentional to allow future extensibility
(e.g., adding options like `{ required: true }`). This commit aligns
the spec documentation with the actual TypeScript types in spec.types.ts.
Fixes validation error:
`{ code: 'invalid_type', expected: 'object', received: 'boolean',
path: ['permissions', 'clipboardWrite'] }`
* fix: Move CSP to HTTP headers + add worker-src, frameDomains, baseUriDomains, permissions
Security improvements:
- CSP is now set via HTTP headers in serve.ts instead of meta tags
(meta tag CSP can be tampered with by same-origin content)
- CSP passed as query param to sandbox.html for header-based enforcement
New CSP/permissions features (borrowed from PR #158):
- frameDomains: control frame-src directive for nested iframes
- baseUriDomains: control base-uri directive
- permissions: camera, microphone, geolocation via iframe allow attribute
WebGL fix:
- Use document.write() instead of srcdoc for inner iframe content
(srcdoc creates opaque origin that breaks WebGL canvas updates)
- Add worker-src directive with blob: support (critical for WebGL apps
like CesiumJS/Three.js that use workers for tile decoding, terrain
processing, image processing)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: Remove permissions support (defer to PR #158)
Keep this PR focused on CSP security fixes only.
Permissions (camera, microphone, geolocation) will be
handled in #158.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* cleanup McpUiResourceCsp type usage / duplicate defs
* fix: guard against CSP injection in domain parameters
Validate CSP domain entries to reject characters that could:
- Break out of CSP directives (semicolons, newlines)
- Inject CSP keywords like 'unsafe-eval' (quotes)
- Inject multiple sources in one entry (spaces)
This prevents injection attacks where malicious domains could
override the security policy.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* docs: add tool visibility and restructure _meta.ui
- Restructure tool metadata: `_meta["ui/resourceUri"]` → `_meta.ui.resourceUri`
- Add `visibility` array field: ["model"], ["apps"], or ["model", "apps"]
- Default ["model"] preserves standard MCP behavior
- ["apps"] enables widget-only tools hidden from agent
- Add McpUiToolMeta interface for type safety
- Add Design Decision #4 explaining approach vs OpenAI's two-field model
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* address PR review comments
- Change default visibility to ["model", "apps"]
- Add deprecation notice for flat ui/resourceUri format
- Add McpUiToolMeta and McpUiToolVisibility to spec.types.ts
- Improve tools/list and tools/call behavior wording
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: regenerate schemas for McpUiToolMeta types
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: tool visibility and nested _meta.ui format
- Rename visibility "apps" → "app" in McpUiToolVisibility type
- Add _meta.ui.resourceUri nested format (deprecate flat format)
- Add getToolUiResourceUri() utility with backward compatibility
- Add visibility demo to system-monitor-server:
- get-system-stats: visibility ["model"] with resourceUri
- refresh-stats: visibility ["app"] (app-only polling)
- Update all example servers to use new _meta.ui format
- Add 11 unit tests for getToolUiResourceUri()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: regenerate schemas with updated dependencies
* feat: export McpUiToolMeta type and add type annotations
- Export McpUiToolMeta and McpUiToolVisibility types from types.ts
- Export corresponding Zod schemas (McpUiToolMetaSchema, McpUiToolVisibilitySchema)
- Add `as McpUiToolMeta` type annotations to all example servers
- Update docs/quickstart.md with proper typing
Ensures type safety for `_meta.ui` tool metadata across the codebase.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: add server helpers and optional connect() transport
Add `src/server/` with convenience functions for registering MCP App tools
and resources:
- `registerAppTool(server, name, config, handler)`
- `registerAppResource(server, name, uri, config, callback)`
The `transport` parameter in `App.connect()` is now optional, defaulting to
`PostMessageTransport(window.parent)`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update src/server/index.ts
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
* feat: update McpUiAppToolConfig to support both _meta.ui and flat formats
* refactor: simplify server helper imports
* docs: add deprecation notice and improve resourceUri documentation
* feat: add backward compat normalization in registerAppTool
- If _meta.ui.resourceUri is set, also set legacy flat key
- If legacy flat key is set, also set _meta.ui.resourceUri
- Preserves existing visibility when merging
- Does not overwrite if both formats already set
* refactor: avoid mutating config arg in registerAppTool
* style: format with prettier
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
The spec used `ui/notifications/sandbox-ready` but the implementation
uses `ui/notifications/sandbox-proxy-ready`. Updated the spec to match.
Also fixed a typo: `ui/sandbox-resource-ready` → `ui/notifications/sandbox-resource-ready`
Fixes#156🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
This adds the media-src directive to the Content Security Policy to enable
MCP Apps to play video and audio content.
Changes:
- Add media-src to the restrictive default CSP in the spec
- Add media-src to the CSP construction from metadata in the spec
- Update resourceDomains documentation in spec to include media
- Update resourceDomains JSDoc in src/types.ts to include media-src
Fixesmodelcontextprotocol/ext-apps#106
Update `ui/host-context-change` to `ui/notifications/host-context-changed`
to match the SDK implementation and follow the notifications naming convention.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Change MIME type from `text/html+mcp` to `text/html;profile=mcp-app`
to follow RFC 6838 recommendations for custom media types.
The `+suffix` format (like `text/html+mcp`) is intended for structured
syntax suffixes (e.g., `+json`, `+xml`) that describe the underlying
format. Since MCP Apps doesn't define a new syntax but rather a profile
of HTML, using a profile parameter is more semantically correct.
Fixes#59🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Safe area insets apply on web as well, not just mobile devices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The host should resolve "system" to either "light" or "dark" before
passing the theme to the app. Apps only need to know the actual
resolved theme value.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
Change connect_domains and resource_domains to connectDomains and
resourceDomains for consistency with the rest of the specification
which uses camelCase naming convention.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The UIResource interface incorrectly specified `_meta?: UIResourceMeta`
when it should be `_meta?: { ui?: UIResourceMeta }` to match the
resources/read response format and all usage examples throughout the
document.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace inline comments with comprehensive TypeDoc comments that explain
the purpose and usage of each property. Add examples and clarify
constraints (MUST/SHOULD) for better developer understanding.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>