Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions catalogs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>/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/<version>/catalogs/`.

Expand Down
24 changes: 22 additions & 2 deletions catalogs/mcp/README.md
Original file line number Diff line number Diff line change
@@ -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`.

Expand Down Expand Up @@ -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 |
Expand Down
315 changes: 315 additions & 0 deletions catalogs/mcp/v1/catalog.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
}
}
Loading
Loading