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
10 changes: 9 additions & 1 deletion catalogs/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ For `updateDataModel`, keys starting with `/` are absolute paths, while relative

## 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 `callMcpTool`, with argument and return types taken from the v1.0 `common_types.json`, and the `McpApp` component.
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 `callMcpTool` and `updateDataModel`, with argument and return types taken from the v1.0 `common_types.json`, and the `McpApp` component.

### Data functions

| Function | Arguments | Returns |
| :---------------- | :-------- | :-------------------------------------------------------------------------- |
| `updateDataModel` | `updates` | Nothing. Writes each key-value pair in `updates` to the surface data model. |

The `updates` argument is a required `DynamicValue`, so it can be a literal object, a `@path` binding or a nested `@call`; a `null` or missing argument is rejected. 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). `updateDataModel` does not evaluate `@path` or `@call` objects found inside the values it writes: a tool result is data, not an expression.

### McpApp component

Expand Down
27 changes: 26 additions & 1 deletion catalogs/mcp/v1/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"$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 the `callMcpTool` function and the `McpApp` component that renders a sandboxed MCP App connected to the host through the MCP Apps bridge.",
"description": "A2UI catalog for Model Context Protocol (MCP) integrations. It defines the `callMcpTool` function, the `updateDataModel` function that writes values to the surface data model, 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": {
Expand Down Expand Up @@ -146,6 +146,28 @@
}
},
"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": {
Expand All @@ -163,6 +185,9 @@
"oneOf": [
{
"$ref": "#/functions/callMcpTool"
},
{
"$ref": "#/functions/updateDataModel"
}
]
}
Expand Down
1 change: 1 addition & 0 deletions typescript/catalogs/mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Emit `'1.0'` as `version` on synthesized `createSurface` and `updateComponents` messages in `callMcpTool` when creating a standalone MCP App surface, matching the unified v1.0 protocol version wire format. [#3097](https://github.com/a2ui-project/a2ui/issues/3097)
- The published package contains only the runtime build output; test files and TypeScript build info are no longer included. [#3348](https://github.com/a2ui-project/a2ui/pull/3348)
- **BREAKING CHANGE**: Hosts set up the catalog once with `configureMcpCatalog`; `createCallMcpToolImplementation`, `CallMcpToolApi` and `A2UI_MIME_TYPE` are removed and `MCP_CATALOG_ID` now identifies the v1.0 catalog (`catalogs/mcp/v1/catalog.json`, bundled as `@a2ui/catalog-mcp/catalog.json`). `callMcpTool` now hands `ui://` MCP App resources to the host. [#3186](https://github.com/a2ui-project/a2ui/pull/3186)
- The `updateDataModel` data function writes key-value pairs to the surface data model; its `UpdateDataModelApi` and `UpdateDataModelImplementation` are exported for hosts that register functions one by one. [#3573](https://github.com/a2ui-project/a2ui/pull/3573)
- Add `mcpCatalog`, the v1.0 MCP catalog with the `McpApp` universal component (`A2uiMcpApp`) and `callMcpTool`, ready to hand to a `MessageProcessor`. `McpApp` renders an MCP App in the shared sandbox, binds host data through `data.paths` and dispatches the tools it is allowed to call as A2UI actions. [#2799](https://github.com/a2ui-project/a2ui/pull/2799)

## 0.0.1-alpha.1
Expand Down
24 changes: 18 additions & 6 deletions typescript/catalogs/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Experimental.** This package is under active development. Its API and the catalog schema may change between releases without notice.

The A2UI MCP catalog lets A2UI surfaces invoke [Model Context Protocol](https://modelcontextprotocol.io/) tools and render [MCP Apps](https://github.com/modelcontextprotocol/ext-apps). It defines the `callMcpTool` function and the `McpApp` component, 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, write values to the data model, and render [MCP Apps](https://github.com/modelcontextprotocol/ext-apps). It defines the `callMcpTool` function, the `updateDataModel` data function, and the `McpApp` component, allowing agents to emit declarative payloads whose controls and bindings interact with MCP servers directly.

## Overview

Expand All @@ -11,6 +11,7 @@ A2UI separates UI layout from backend logic through catalogs. This catalog provi
- The host supplies one hook, `getMcpClientForTool(toolName)`. The catalog uses the returned client for tool execution, UI resource reads, and tool discovery.
- Tool results are processed automatically. The catalog fetches and caches UI resources referenced by results, then applies any A2UI messages embedded in the result content.
- Tool arguments support data bindings, allowing form state to pass directly into tool calls.
- `updateDataModel` writes path-value pairs to the surface data model, so a payload can update several paths at once from a literal object, a binding or a function call.
- The `McpApp` component embeds MCP Apps in a sandboxed double-iframe and connects them to the host through `AppBridge`.
- The catalog works with any A2UI web renderer built on `MessageProcessor`.

Expand All @@ -30,6 +31,14 @@ Tools are addressed by name only. A2UI payloads never name a server, because mul

The function returns the raw MCP `CallToolResult`. It throws an `A2uiExpressionError` if the client cannot be resolved, the call returns no result, or the result has `isError: true`.

`updateDataModel` writes path-value pairs to the surface data model:

| Function | Arguments | Returns |
| :---------------- | :---------------------- | :-------------------------------------------------------------------------- |
| `updateDataModel` | `updates: DynamicValue` | Nothing. Writes each key-value pair in `updates` to the surface data model. |

The `updates` argument is required, and a `null` or missing argument is rejected by validation. `updateDataModel` throws an `A2uiExpressionError` for `updates` that are not a non-null plain object, or for a key that is empty or contains a `__proto__`, `constructor` or `prototype` segment. It checks every key before it writes anything, so a rejected key leaves the data model unchanged. Keys starting with `/` are absolute paths and relative keys resolve against the calling data context, such as the current row of a template list. It does not evaluate `@path` or `@call` objects found inside the values it writes: a tool result is data, not an expression.

## Installation

```bash
Expand Down Expand Up @@ -70,28 +79,30 @@ configureMcpCatalog({

Push every catalog before the first message arrives, since `getClientCapabilities` reports whatever the array holds when it is called.

A surface resolves components through its default catalog, the `catalogId` of its `createSurface` message, unless a component names another registered catalog with its own `catalogId`. A payload that mixes basic components with `McpApp` therefore needs no composed catalog: it creates the surface with the basic catalog id and sets `"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json"` on the `McpApp` component, as the [catalog examples](../../../catalogs/mcp/v1/examples) do.
A surface resolves components and function calls through its default catalog, the `catalogId` of its `createSurface` message, unless the component or the `@call` object names another registered catalog with its own `catalogId`. A payload that mixes basic components with `McpApp` and the MCP functions therefore needs no composed catalog: it creates the surface with the basic catalog id and sets `"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json"` on the `McpApp` component and on each MCP function call, as the [catalog examples](../../../catalogs/mcp/v1/examples) do.

Function calls in expressions (`${...}`, `@call`) are the exception: they always resolve through the surface's default catalog. A payload that calls `callMcpTool` from expressions next to basic components creates its surface with a catalog that carries both; build one under the id the agent uses (`mcpCatalog.id` is the canonical one):

```typescript
import {basicCatalog, Catalog, MessageProcessor} from '@a2ui/web_core/v1_0';
import {CallMcpToolImplementation, configureMcpCatalog, mcpCatalog} from '@a2ui/catalog-mcp';
import {configureMcpCatalog, mcpCatalog} from '@a2ui/catalog-mcp';

const processor = new MessageProcessor(
[
new Catalog(
mcpCatalog.id,
'1.0',
[...basicCatalog.components.values(), ...mcpCatalog.components.values()],
[...basicCatalog.functions.values(), CallMcpToolImplementation],
[...basicCatalog.functions.values(), ...mcpCatalog.functions.values()],
),
],
onAction,
);
configureMcpCatalog({getMcpClientForTool: () => client, processor});
```

A host that only wants some of the functions registers the `*Implementation` exports it needs (`CallMcpToolImplementation`, `UpdateDataModelImplementation`) in a catalog of its own instead of `mcpCatalog`.

For multi-server setups, keep a registry of which server advertises each tool, usually built from `listTools()` at connection time, and resolve it in the same hook:

```typescript
Expand Down Expand Up @@ -318,10 +329,11 @@ The component owns its bridge: `McpAppBridge` (`src/components/mcp_app_bridge.ts
| File | Responsibility |
| :------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/catalog.json` | Copy of `catalogs/mcp/v1/catalog.json` made by the `copy-catalog` build step |
| `src/index.ts` | Package entry: `mcpCatalog`, `A2uiMcpApp`, `configureMcpCatalog` and `CallMcpToolImplementation` |
| `src/catalog.ts` | `MCP_CATALOG_ID` and `mcpCatalog`: the `McpApp` component and `callMcpTool` |
| `src/index.ts` | Package entry: `mcpCatalog`, `A2uiMcpApp`, `configureMcpCatalog` and the `*Api` and `*Implementation` of every function |
| `src/catalog.ts` | `MCP_CATALOG_ID` and `mcpCatalog`: the `McpApp` component, `callMcpTool` and `updateDataModel` |
| `src/mcp_catalog_config.ts` | `configureMcpCatalog`: the host's MCP client resolver, message processor and sandbox location |
| `src/functions/callMcpTool.ts` | MCP tool execution, UI resource discovery, caching, and message decoding |
| `src/functions/update_data_model.ts` | `updateDataModel` |
| `src/dynamic-values.ts` | Resolution of dynamic values nested in literal containers |
| `src/components/mcp_app.ts` | The `McpApp` universal component and its API |
| `src/components/mcp_app_bridge.ts` | `McpAppBridge`: `AppBridge` setup, tool calls, function calls and data model sync |
Expand Down
2 changes: 2 additions & 0 deletions typescript/catalogs/mcp/src/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import catalogJson from './catalog.json' with {type: 'json'};
import {MCP_CATALOG_ID, mcpCatalog} from './catalog.js';
import {A2uiMcpApp} from './components/mcp_app.js';
import {CallMcpToolImplementation} from './functions/callMcpTool.js';
import {UpdateDataModelImplementation} from './functions/update_data_model.js';
import {EXAMPLE_CATALOGS, parseExampleMessages} from './testing/frame_test_support.js';

const toolCallExample = {
Expand Down Expand Up @@ -127,6 +128,7 @@ describe('mcpCatalog', () => {
Object.keys(catalogJson['functions']).sort(),
);
expect(mcpCatalog.functions.get('callMcpTool')).toBe(CallMcpToolImplementation);
expect(mcpCatalog.functions.get('updateDataModel')).toBe(UpdateDataModelImplementation);
});

it('registers universal components with a2ui- prefixed tag names', () => {
Expand Down
10 changes: 6 additions & 4 deletions typescript/catalogs/mcp/src/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,23 @@
import {Catalog, type WebComponentImplementation} from '@a2ui/web_core/v1_0';
import {A2uiMcpApp} from './components/mcp_app.js';
import {CallMcpToolImplementation} from './functions/callMcpTool.js';
import {UpdateDataModelImplementation} from './functions/update_data_model.js';

/** Identifier of the A2UI MCP catalog schema (`catalogs/mcp/v1/catalog.json`). */
export const MCP_CATALOG_ID = 'https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json';

/**
* The MCP catalog: the `McpApp` component and `callMcpTool`, ready to hand to a
* The MCP catalog: the `McpApp` component, `callMcpTool`, and `updateDataModel`, ready to hand
* to a
* `MessageProcessor`. `callMcpTool` runs once the host has called
* `configureMcpCatalog({getMcpClientForTool, processor})`.
*
* Register it next to the other catalogs of the host; a component of a surface created with
* another catalog selects this one with its own `catalogId`.
* Register it next to the other catalogs of the host; a component or function call of a surface
* created with another catalog selects this one with its own `catalogId`.
*/
export const mcpCatalog = new Catalog<WebComponentImplementation>(
MCP_CATALOG_ID,
'1.0',
[A2uiMcpApp],
[CallMcpToolImplementation],
[CallMcpToolImplementation, UpdateDataModelImplementation],
);
8 changes: 7 additions & 1 deletion typescript/catalogs/mcp/src/functions/callMcpTool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,17 @@ import {
type McpToolClient,
} from './callMcpTool.js';
import mcpCatalogJson from '../catalog.json' with {type: 'json'};
import {UpdateDataModelImplementation} from './update_data_model.js';

const SURFACE_CATALOG_ID = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';

/** The functions this package implements, as a host registers them. */
const mcpCatalog = new Catalog<any>(MCP_CATALOG_ID, '1.0', [], [CallMcpToolImplementation]);
const mcpCatalog = new Catalog<any>(
MCP_CATALOG_ID,
'1.0',
[],
[CallMcpToolImplementation, UpdateDataModelImplementation],
);

/** A function that returns `{path: data}`, the shape a tool argument named `path` needs. */
const PathArgumentImplementation = createFunctionImplementation(
Expand Down
Loading
Loading