From 5a87480f35d62029e069978e4dbe7fbab4838dea Mon Sep 17 00:00:00 2001 From: Jose Montes Date: Wed, 7 Oct 2026 22:58:01 +0000 Subject: [PATCH] feat(catalogs): add the v1 MCP catalog with the McpApp component schema Add catalogs/mcp/v1/catalog.json, the MCP catalog for A2UI protocol v1.0: the existing callMcpTool and data functions plus the McpApp component, whose allowedFunctions is a map of function name to JSON Schema. Move the component specification next to it, add examples, validate them in specification/scripts/validate.py, and publish the catalog at its $id URL from the docs workflow. The v0.9 catalog at catalogs/mcp/catalog.json is unchanged. --- .github/workflows/docs.yml | 3 + catalogs/README.md | 8 +- catalogs/mcp/README.md | 24 +- catalogs/mcp/v1/catalog.json | 315 ++++++++++++++++++ .../mcp/v1/mcp_app_specification.md | 123 +++---- conformance/core/catalog.yaml | 8 + specification/v1_0/test/run_tests.py | 12 + .../tests/conformance/conformance_test.mjs | 1 + 8 files changed, 414 insertions(+), 80 deletions(-) create mode 100644 catalogs/mcp/v1/catalog.json rename samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md => catalogs/mcp/v1/mcp_app_specification.md (84%) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e2fc3907d6..3a40d297bb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -114,6 +114,9 @@ jobs: mkdir -p docs/public/specification/v1_0/catalogs/basic cp catalogs/basic/v1/catalog.json docs/public/specification/v1_0/catalogs/basic/ cp -R catalogs/basic/v1/examples docs/public/specification/v1_0/catalogs/basic/ + # The MCP catalog v1 targets protocol v1.0. + mkdir -p docs/public/specification/v1_0/catalogs/mcp + cp catalogs/mcp/v1/catalog.json docs/public/specification/v1_0/catalogs/mcp/ # Flatten JSON files to version roots to support clean URLs without redirects for version in v0_8 v0_9 v0_9_1 v1_0; do if [ -d "docs/public/specification/$version/json" ]; then diff --git a/catalogs/README.md b/catalogs/README.md index 5ed2f46259..b7a995d3a6 100644 --- a/catalogs/README.md +++ b/catalogs/README.md @@ -18,7 +18,10 @@ catalogs/ │ ├── examples/ │ └── basic_catalog_implementation_guide.md └── mcp/ # MCP catalog - └── catalog.json + ├── catalog.json # Pre-1.0 MCP catalog, protocol v0.9 + └── v1/ # MCP catalog, major version 1 (protocol v1.0) + ├── catalog.json + └── mcp_app_specification.md ``` - Catalogs are identified by the `$id` declared inside `catalog.json`, not by @@ -29,7 +32,8 @@ catalogs/ - `basic/` is versioned by catalog major version (`v1/`), which is independent of the protocol version. Newer catalogs start unversioned (`catalogs//catalog.json`) and add a version directory only when - they need a breaking change. + they need a breaking change. The MCP catalog for protocol v0.9 stays at + `catalogs/mcp/catalog.json`; its v1.0 successor lives in `mcp/v1/`. - Basic catalogs for protocol versions before v1.0 remain under `specification//catalogs/`. diff --git a/catalogs/mcp/README.md b/catalogs/mcp/README.md index 0b6c626cd3..5760b91946 100644 --- a/catalogs/mcp/README.md +++ b/catalogs/mcp/README.md @@ -1,8 +1,8 @@ # A2UI MCP catalog -The A2UI MCP catalog lets A2UI surfaces invoke [Model Context Protocol](https://modelcontextprotocol.io/) tools and transform tool results into data model updates. It defines `callMcpTool` and five data functions, allowing agents to emit declarative payloads whose controls and bindings interact with MCP servers directly. +The A2UI MCP catalog lets A2UI surfaces invoke [Model Context Protocol](https://modelcontextprotocol.io/) tools and transform tool results into data model updates. It defines `callMcpTool` and five data functions, allowing agents to emit declarative payloads whose controls and bindings interact with MCP servers directly. Its v1 catalog adds the `McpApp` component, which renders a sandboxed [MCP App](https://github.com/modelcontextprotocol/ext-apps) inside a surface. -## Catalog specification +## Catalog specification (protocol v0.9) The catalog ID is `https://a2ui.org/specification/v0_9/catalogs/mcp/mcp_catalog.json`. @@ -33,6 +33,26 @@ To test whether a string matches a pattern, use the basic catalog's `regex` func For `updateDataModel`, keys starting with `/` are absolute paths, while relative keys resolve against the calling data context (such as the current row scope inside a template list). +## v1 catalog (protocol v1.0) + +The v1 catalog, [v1/catalog.json](v1/catalog.json), targets A2UI protocol v1.0. Its catalog ID is `https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json`. It declares the same `callMcpTool` and data functions as above, with argument and return types taken from the v1.0 `common_types.json`, and adds the `McpApp` component. The five data functions are not MCP specific and are slated to move to a separate transformations catalog ([#3153](https://github.com/a2ui-project/a2ui/issues/3153)). + +### McpApp component + +`McpApp` renders an MCP App in a double-iframe sandbox and connects it to the surface through the MCP Apps JSON-RPC bridge. Its properties are declared in [v1/catalog.json](v1/catalog.json): + +| Property | Type | Required | Description | +| :----------------- | :----------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `htmlContent` | `DynamicString` | Yes | The HTML of the app, rendered through `srcdoc`. A value prefixed with `url_encoded:` is decoded first. | +| `title` | `DynamicString` | No | The accessible title of the frame. | +| `allowedTools` | `array` of `string` | No | The MCP tools the app may call. The host dispatches an authorized `tools/call` request as an A2UI action named after the tool (and executes a configured `callTool` callback when provided). | +| `allowedFunctions` | `object` of JSON Schemas | No | The catalog functions the app may call through `ui/requests/function-call`, each mapped to the schema of its arguments. | +| `data.paths` | `object` of `string` | No | A map of state keys (or reserved keys `toolInput`, `toolResult`, `modelContext`) to JSON Pointer paths in the data model. The host pushes changes to the app and writes the app's data changes back. | +| `csp` | `object` | No | Per-resource Content Security Policy domain allowlists (`connectDomains`, `resourceDomains`, `frameDomains`, `baseUriDomains`) from `_meta.ui.csp`. | +| `permissions` | `object` | No | Browser capability permissions (`camera`, `microphone`, `geolocation`, `clipboardWrite`) delegated to the inner sandboxed iframe from `_meta.ui.permissions`. | + +Tool calls and function calls that are not listed are rejected with a JSON-RPC error. The bridge protocol, the sandbox layout and the security controls are defined in the [MCP App component specification](v1/mcp_app_specification.md). + ## Implementations | Language | Package | diff --git a/catalogs/mcp/v1/catalog.json b/catalogs/mcp/v1/catalog.json new file mode 100644 index 0000000000..aabb965571 --- /dev/null +++ b/catalogs/mcp/v1/catalog.json @@ -0,0 +1,315 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json", + "protocolVersion": "1.0", + "title": "A2UI MCP Catalog", + "description": "A2UI catalog for Model Context Protocol (MCP) integrations. It defines `callMcpTool` and data functions that transform tool results into data model updates, and the `McpApp` component that renders a sandboxed MCP App connected to the host through the MCP Apps bridge.", + "catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json", + "components": { + "McpApp": { + "type": "object", + "description": "Renders a sandboxed Model Context Protocol application using double-iframe isolation. The application talks to the host over the MCP Apps JSON-RPC bridge; tool calls it is allowed to make are dispatched as A2UI actions.", + "properties": { + "component": { + "const": "McpApp" + }, + "htmlContent": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The HTML content of the app, rendered via srcdoc inside the sandbox. A value prefixed with `url_encoded:` is decoded with decodeURIComponent before rendering." + }, + "title": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The title of the app, used as the accessible title of the frame." + }, + "allowedTools": { + "type": "array", + "description": "The names of the MCP tools the app is allowed to call. The host dispatches an authorized `tools/call` request as an A2UI action named after the tool, with the tool arguments as its context, and rejects any other tool call.", + "items": { + "type": "string" + } + }, + "allowedFunctions": { + "type": "object", + "description": "A map of authorized host client function names to JSON Schemas describing their expected arguments. The host rejects a `ui/requests/function-call` request whose function is not listed or whose arguments fail validation. The functions must exist in the surface's catalog.", + "additionalProperties": { + "type": "object", + "description": "A JSON Schema. The host validates the matching payload against it before acting on the message; an empty object accepts any value." + } + }, + "data": { + "type": "object", + "description": "Data binding configuration for the component. Reserved keys `toolInput`, `toolResult`, and `modelContext` map to standard MCP Apps `ui/notifications/tool-input`, `ui/notifications/tool-result`, and `ui/update-model-context` messages.", + "properties": { + "paths": { + "type": "object", + "description": "A dictionary mapping custom state keys (or reserved keys `toolInput`, `toolResult`, `modelContext`) to distinct JSON Pointer paths in the data model. The host pushes a `ui/notifications/data-model-update` notification whenever a bound value changes and writes authorized `ui/notifications/data-model-change` notifications back to the path.", + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["paths"], + "additionalProperties": false + }, + "csp": { + "type": "object", + "description": "Per-resource Content Security Policy domain allowlists from `_meta.ui.csp`.", + "properties": { + "connectDomains": { + "type": "array", + "description": "Origins allowed in `connect-src` (fetch, XHR, WebSocket).", + "items": { + "type": "string" + } + }, + "resourceDomains": { + "type": "array", + "description": "Origins allowed for scripts, styles, images, fonts, and media.", + "items": { + "type": "string" + } + }, + "frameDomains": { + "type": "array", + "description": "Origins allowed for nested iframes (`frame-src`).", + "items": { + "type": "string" + } + }, + "baseUriDomains": { + "type": "array", + "description": "Origins allowed for document `base-uri`.", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "permissions": { + "type": "object", + "description": "Browser capability permissions delegated to the inner sandboxed iframe from `_meta.ui.permissions`.", + "properties": { + "camera": { + "type": ["boolean", "object"], + "description": "Delegates `camera` access via the iframe `allow` attribute." + }, + "microphone": { + "type": ["boolean", "object"], + "description": "Delegates `microphone` access via the iframe `allow` attribute." + }, + "geolocation": { + "type": ["boolean", "object"], + "description": "Delegates `geolocation` access via the iframe `allow` attribute." + }, + "clipboardWrite": { + "type": ["boolean", "object"], + "description": "Delegates `clipboard-write` access via the iframe `allow` attribute." + } + }, + "additionalProperties": false + } + }, + "required": ["component", "htmlContent"] + } + }, + "functions": { + "callMcpTool": { + "type": "object", + "description": "Invokes a tool on a connected Model Context Protocol (MCP) server and returns the result. Can be combined with `jmespath` and `updateDataModel` to update the surface data model from tool output.", + "returnType": "any", + "properties": { + "@call": { + "const": "callMcpTool" + }, + "args": { + "type": "object", + "properties": { + "name": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The name of the MCP tool to execute." + }, + "arguments": { + "type": "object", + "description": "The arguments to pass to the MCP tool.", + "additionalProperties": { + "$ref": "common_types.json#/$defs/DynamicValue" + } + }, + "targetDataPath": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "Optional JSON Pointer path on the surface data model where a resolved MCP App resource payload (`uri`, `htmlContent`, `csp`, `permissions`, `toolInput`, `toolResult`) is written." + } + }, + "required": ["name"], + "additionalProperties": false + } + }, + "required": ["@call", "args"] + }, + "jmespath": { + "type": "object", + "description": "Evaluates a JMESPath expression against input data.", + "returnType": "any", + "properties": { + "@call": { + "const": "jmespath" + }, + "args": { + "type": "object", + "properties": { + "expression": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "A standard JMESPath expression evaluated against `data`." + }, + "data": { + "$ref": "common_types.json#/$defs/DynamicValue", + "description": "The input data to evaluate the expression against." + } + }, + "required": ["expression", "data"], + "additionalProperties": false + } + }, + "required": ["@call", "args"] + }, + "split": { + "type": "object", + "description": "Splits a string or array of strings by a separator.", + "returnType": "any", + "properties": { + "@call": { + "const": "split" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicValue", + "description": "The string or array of strings to split." + }, + "separator": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The delimiter string to split on. An empty string splits into individual characters." + } + }, + "required": ["value", "separator"], + "additionalProperties": false + } + }, + "required": ["@call", "args"] + }, + "regexCapture": { + "type": "object", + "description": "Extracts capture groups from the first RE2 regular expression match.", + "returnType": "any", + "properties": { + "@call": { + "const": "regexCapture" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicValue", + "description": "The string or array of strings to match against." + }, + "pattern": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "An RE2 regular expression. Returns an array of capture groups from the first match, or null if there is no match. Unmatched optional groups return empty strings." + } + }, + "required": ["value", "pattern"], + "additionalProperties": false + } + }, + "required": ["@call", "args"] + }, + "regexReplace": { + "type": "object", + "description": "Replaces all matches of an RE2 regular expression with literal text.", + "returnType": "any", + "properties": { + "@call": { + "const": "regexReplace" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicValue", + "description": "The string or array of strings to perform replacements on." + }, + "pattern": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "An RE2 regular expression matching substrings to replace." + }, + "replacement": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "Literal replacement string. Capture group references like $1 are treated as literal text." + } + }, + "required": ["value", "pattern", "replacement"], + "additionalProperties": false + } + }, + "required": ["@call", "args"] + }, + "updateDataModel": { + "type": "object", + "description": "Writes key-value pairs to the surface data model.", + "returnType": "any", + "properties": { + "@call": { + "const": "updateDataModel" + }, + "args": { + "type": "object", + "properties": { + "updates": { + "$ref": "common_types.json#/$defs/DynamicValue", + "description": "An object mapping data model paths to the values to write (e.g., {\"/entries\": [...], \"/title\": \"Home\"}). Paths starting with \"/\" are absolute; relative paths resolve against the current data context." + } + }, + "required": ["updates"], + "additionalProperties": false + } + }, + "required": ["@call", "args"] + } + }, + "$defs": { + "anyComponent": { + "oneOf": [ + { + "$ref": "#/components/McpApp" + } + ], + "discriminator": { + "propertyName": "component" + } + }, + "anyFunction": { + "oneOf": [ + { + "$ref": "#/functions/callMcpTool" + }, + { + "$ref": "#/functions/jmespath" + }, + { + "$ref": "#/functions/split" + }, + { + "$ref": "#/functions/regexCapture" + }, + { + "$ref": "#/functions/regexReplace" + }, + { + "$ref": "#/functions/updateDataModel" + } + ] + } + } +} diff --git a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md b/catalogs/mcp/v1/mcp_app_specification.md similarity index 84% rename from samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md rename to catalogs/mcp/v1/mcp_app_specification.md index f076157daa..d643e4bbb8 100644 --- a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md +++ b/catalogs/mcp/v1/mcp_app_specification.md @@ -1,4 +1,4 @@ -# A2UI MCP App Component Specification (v0.9) +# A2UI MCP App Component Specification (v1.0) ## A Specification for Sandboxed, Model Context Protocol Components in the Agent-to-UI Protocol @@ -10,7 +10,7 @@ Status: In progress This specification defines the A2UI MCP App Component (`McpApp`) for the secure, sandboxed rendering of Model Context Protocol (MCP) applications in the Agent-to-UI (A2UI) protocol. This document has two purposes: 1. **Platform implementation blueprint:** It provides client-side platform developers with instructions to implement compliant `McpApp` components in any native rendering framework (such as Lit or Angular) while maintaining security guarantees. -2. **Interoperable application standard:** It defines the communication interface and message schemas between the A2UI host and the sandboxed MCP application. It uses the `@modelcontextprotocol/ext-apps/app-bridge` specification to support A2UI v0.9 capabilities, including local client-side function execution and two-way local data binding. +2. **Interoperable application standard:** It defines the communication interface and message schemas between the A2UI host and the sandboxed MCP application. It uses the `@modelcontextprotocol/ext-apps/app-bridge` specification to support A2UI v1.0 capabilities, including local client-side function execution and two-way local data binding. # 1. Introduction and motivation @@ -22,7 +22,7 @@ The A2UI protocol is designed to stream structured, type-safe JSON component tre The `McpApp` component resolves these needs. It runs the embedded application inside a sandboxed double-iframe proxy using the `@modelcontextprotocol/ext-apps/app-bridge` library. -In A2UI v0.9, the following features are integrated into the MCP App Bridge: +In A2UI v1.0, the following features are integrated into the MCP App Bridge: 1. **Local client-side function execution:** Allowing the sandboxed application to trigger local host functions (such as system checks or opening links) that are registered in the A2UI catalog. 2. **Two-way local data binding:** Synchronizing state between the MCP application's internal model and the parent A2UI local Data Model without requiring round-trips to the remote agent. @@ -53,21 +53,27 @@ All communications between the host, the sandbox proxy, and the embedded app use ### Protocol Methods Summary -The following table outlines the protocol methods used in the `McpApp` lifecycle and whether they are standard MCP Apps protocol methods or A2UI-specific extensions introduced to support A2UI v0.9 capabilities. - -| Method | Direction | Type | Origin | Purpose | -| ----------------------------------------- | ------------------------ | ------------ | ------------------ | --------------------------------------- | -| `ui/initialize` | App $\rightarrow$ Host | Request | **Standard MCP** | Handshake & capability negotiation | -| `ui/notifications/initialized` | App $\rightarrow$ Host | Notification | **Standard MCP** | Handshake completion | -| `tools/call` | App $\rightarrow$ Host | Request | **Standard MCP** | Remote tool execution | -| `ui/notifications/size-changed` | App $\rightarrow$ Host | Notification | **Standard MCP** | Dynamic frame resizing | -| `notifications/message` | App $\rightarrow$ Host | Notification | **Standard MCP** | Diagnostic logging | -| `ui/notifications/sandbox-proxy-ready` | Proxy $\rightarrow$ Host | Notification | **Standard MCP** | Proxy frame ready signal | -| `ui/notifications/sandbox-resource-ready` | Host $\rightarrow$ Proxy | Notification | **Standard MCP** | Provide HTML for inner frame | -| `ui/notifications/data-model-change` | App $\rightarrow$ Host | Notification | **A2UI Extension** | Two-way local data binding | -| `ui/notifications/data-model-update` | Host $\rightarrow$ App | Notification | **A2UI Extension** | Two-way local data binding | -| `ui/requests/function-call` | App $\rightarrow$ Host | Request | **A2UI Extension** | Local client-side function execution | -| `ui/notifications/host-context-changed` | Host $\rightarrow$ App | Notification | **Standard MCP** | Host context (dimensions, theme) update | +The following table outlines the protocol methods used in the `McpApp` lifecycle and whether they are standard MCP Apps protocol methods or A2UI-specific extensions introduced to support A2UI v1.0 capabilities. + +| Method | Direction | Type | Origin | Purpose | +| ----------------------------------------- | ------------------------ | ------------ | ------------------ | ---------------------------------------------------- | +| `ui/initialize` | App $\rightarrow$ Host | Request | **Standard MCP** | Handshake & capability negotiation | +| `ui/notifications/initialized` | App $\rightarrow$ Host | Notification | **Standard MCP** | Handshake completion | +| `tools/call` | App $\rightarrow$ Host | Request | **Standard MCP** | Remote tool execution & A2UI action dispatch | +| `ui/notifications/tool-input` | Host $\rightarrow$ App | Notification | **Standard MCP** | Deliver initial/updated tool arguments (`toolInput`) | +| `ui/notifications/tool-result` | Host $\rightarrow$ App | Notification | **Standard MCP** | Deliver tool execution output (`toolResult`) | +| `ui/update-model-context` | App $\rightarrow$ Host | Request | **Standard MCP** | Synchronize widget context (`modelContext`) | +| `ui/open-link` | App $\rightarrow$ Host | Request | **Standard MCP** | Open a validated `http:`/`https:` URL in a new tab | +| `ui/message` | App $\rightarrow$ Host | Request | **Standard MCP** | Send a chat/follow-up message as an A2UI action | +| `ui/request-display-mode` | App $\rightarrow$ Host | Request | **Standard MCP** | Negotiate display mode (`inline`) | +| `ui/notifications/size-changed` | App $\rightarrow$ Host | Notification | **Standard MCP** | Dynamic frame resizing | +| `notifications/message` | App $\rightarrow$ Host | Notification | **Standard MCP** | Diagnostic logging | +| `ui/notifications/sandbox-proxy-ready` | Proxy $\rightarrow$ Host | Notification | **Standard MCP** | Proxy frame ready signal | +| `ui/notifications/sandbox-resource-ready` | Host $\rightarrow$ Proxy | Notification | **Standard MCP** | Provide HTML, CSP, and permissions for inner frame | +| `ui/notifications/data-model-change` | App $\rightarrow$ Host | Notification | **A2UI Extension** | Two-way local data binding | +| `ui/notifications/data-model-update` | Host $\rightarrow$ App | Notification | **A2UI Extension** | Two-way local data binding | +| `ui/requests/function-call` | App $\rightarrow$ Host | Request | **A2UI Extension** | Local client-side function execution | +| `ui/notifications/host-context-changed` | Host $\rightarrow$ App | Notification | **Standard MCP** | Host context (dimensions, theme) update | ## 3.1. Handshake lifecycle @@ -163,12 +169,18 @@ Dispatched when the embedded application requests an MCP tool execution (represe ``` **Host action** -The host checks if the requested tool name is in the component's `allowedTools` list. If it is authorized, the host dispatches the tool call as an A2UI action to the agent backend. If not, it rejects the tool call and returns a JSON-RPC error response. +The host checks if the requested tool name is in the component's `allowedTools` list. If it is authorized, the host dispatches the tool call as an A2UI action to the agent backend, and when a direct MCP `callTool` executor is configured on the component or bridge, invokes the MCP tool and returns its `CallToolResult` directly to the embedded application. If the tool is not in `allowedTools`, the host rejects the tool call and returns a JSON-RPC error response. -### B. Reactive state synchronization (`ui/notifications/data-model-change`) +### B. Reactive state synchronization (`ui/notifications/data-model-change` and reserved `data.paths` keys) Dispatched when the embedded application updates its internal state and wants to write it back to the parent A2UI Data Model. +In addition to custom state keys, `data.paths` recognizes three reserved keys that bridge standard MCP Apps notifications and requests to the A2UI surface data model: + +- **`toolInput`**: Bound data model values and updates are sent to the embedded app as standard `ui/notifications/tool-input` notifications (`{arguments}`). +- **`toolResult`**: Bound data model values and updates are wrapped into an MCP `CallToolResult` (`{content, structuredContent}`) and sent to the embedded app as standard `ui/notifications/tool-result` notifications. +- **`modelContext`**: When the embedded app sends a standard `ui/update-model-context` request (`{structuredContent, content}`), the host writes `structuredContent ?? content` to the bound `modelContext` path in the surface data model. + **Message schema** ```json @@ -200,7 +212,7 @@ To avoid infinite update loops and redundant echoes, both sides should implement ### C. Local client-side function execution (`ui/requests/function-call`) -Dispatched when the embedded app wants to execute a registered local A2UI v0.9 function. +Dispatched when the embedded app wants to execute a registered local A2UI v1.0 function. **Message schema** @@ -219,7 +231,7 @@ Dispatched when the embedded app wants to execute a registered local A2UI v0.9 f ``` **Host action** -The host checks if the target function is listed in the component's `allowedFunctions` list. If verified, it evaluates the function using the A2UI client catalog engine and returns the result (or error) to the app. +The host checks if the target function is a key of the component's `allowedFunctions` map and validates `args` against the JSON Schema stored under that key. If verified, it evaluates the function using the A2UI client catalog engine and returns the result (or error) to the app. ### D. Frame resize request (`ui/notifications/size-changed`) @@ -261,6 +273,18 @@ Dispatched when the embedded app publishes diagnostic logging, following standar **Host action** The host logs the diagnostic message to the developer console. +### F. External link navigation (`ui/open-link`) + +Dispatched when the embedded app requests opening an external URL. The host validates that the URL uses `http:` or `https:` (rejecting other schemes such as `javascript:` or `data:` with `InvalidParams`) and opens it in a new tab with `noopener,noreferrer` (or delegates to a host-supplied `openLink` callback). + +### G. Chat message request (`ui/message`) + +Dispatched when the embedded app requests sending a message into the host conversation. The host dispatches an A2UI action named `ui/message` carrying `{role, content}`. + +### H. Display mode negotiation (`ui/request-display-mode`) + +Dispatched when the embedded app requests a display mode change. The host responds with `{mode: 'inline'}`. + ## 3.3. Incoming messages (Host to embedded app) ### A. Reactive state update (`ui/notifications/data-model-update`) @@ -368,60 +392,7 @@ The `McpApp` component is registered in the A2UI Component Catalog. ## 4.1. Schema definition -```json -{ - "McpApp": { - "type": "object", - "description": "Renders a sandboxed Model Context Protocol application using double-iframe isolation.", - "properties": { - "id": { - "$ref": "common_types.json#/$defs/ComponentId" - }, - "component": { - "const": "McpApp" - }, - "htmlContent": { - "$ref": "common_types.json#/$defs/DynamicString", - "description": "The raw HTML string to render via srcdoc. Can be URL-encoded." - }, - "allowedTools": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of MCP tools the embedded application is authorized to request." - }, - "allowedFunctions": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of local client-side functions the embedded application is authorized to call." - }, - "data": { - "type": "object", - "properties": { - "paths": { - "type": "object", - "description": "A dictionary mapping custom state keys to distinct JSON Pointer paths in the data model.", - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["paths"], - "additionalProperties": false - }, - "title": { - "$ref": "common_types.json#/$defs/DynamicString", - "description": "The title attribute for accessibility." - } - }, - "required": ["id", "component"], - "unevaluatedProperties": false - } -} -``` +The normative JSON Schema for `McpApp` is `components.McpApp` in [`catalog.json`](catalog.json), written to the v1.0 Catalog Schema Rules. It declares `htmlContent`, `title`, `allowedTools`, `allowedFunctions`, `data.paths`, `csp` and `permissions`. # 5. Rendering setup and security controls diff --git a/conformance/core/catalog.yaml b/conformance/core/catalog.yaml index def08a71ed..fe7b53a358 100644 --- a/conformance/core/catalog.yaml +++ b/conformance/core/catalog.yaml @@ -1356,3 +1356,11 @@ "@call": openUrl args: {url: "https://example.com"} bogus: 1 + +- name: test_v10_published_mcp_catalog_is_self_contained + description: Tests that the published v1.0 MCP catalog loads with its common_types references localised, so every $ref resolves within the resolved catalog schema. + action: from_json + protocolVersion: "v1.0" + catalogPath: "catalogs/mcp/v1/catalog.json" + expect: + selfContained: true diff --git a/specification/v1_0/test/run_tests.py b/specification/v1_0/test/run_tests.py index a7a95c8741..410846996d 100755 --- a/specification/v1_0/test/run_tests.py +++ b/specification/v1_0/test/run_tests.py @@ -264,6 +264,10 @@ def validate_catalogs_structure(): catalogs_to_validate = [ ("catalogs/basic/v1/catalog.json", BASIC_CATALOG_FILE), + ( + "catalogs/mcp/v1/catalog.json", + os.path.join(REPO_ROOT, "catalogs/mcp/v1/catalog.json"), + ), ("test/testing_catalog.json", os.path.join(TEST_DIR, "testing_catalog.json")), ] @@ -308,6 +312,10 @@ def validate_catalogs_identifiers(): """ catalogs_to_validate = [ ("catalogs/basic/v1/catalog.json", BASIC_CATALOG_FILE), + ( + "catalogs/mcp/v1/catalog.json", + os.path.join(REPO_ROOT, "catalogs/mcp/v1/catalog.json"), + ), ("test/testing_catalog.json", os.path.join(TEST_DIR, "testing_catalog.json")), ] @@ -412,6 +420,10 @@ def validate_catalogs_ref_targets(): """ catalogs_to_validate = [ ("catalogs/basic/v1/catalog.json", BASIC_CATALOG_FILE), + ( + "catalogs/mcp/v1/catalog.json", + os.path.join(REPO_ROOT, "catalogs/mcp/v1/catalog.json"), + ), ("test/testing_catalog.json", os.path.join(TEST_DIR, "testing_catalog.json")), ] diff --git a/typescript/web_core/tests/conformance/conformance_test.mjs b/typescript/web_core/tests/conformance/conformance_test.mjs index ad746542c5..09c537fec9 100644 --- a/typescript/web_core/tests/conformance/conformance_test.mjs +++ b/typescript/web_core/tests/conformance/conformance_test.mjs @@ -164,6 +164,7 @@ const KNOWN_DIVERGENCES = new Map([ 'test_v10_published_basic_catalog_is_self_contained', V10_PUBLISHED_CATALOG_NOT_SELF_CONTAINED, ], + ['test_v10_published_mcp_catalog_is_self_contained', PUBLISHED_CATALOG_NOT_SELF_CONTAINED], [ 'test_v09_published_basic_catalog_rejects_function_call_extra_key', FUNCTION_CALL_EXTRA_KEY_ACCEPTED,