Skip to content
Open
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
19 changes: 13 additions & 6 deletions catalogs/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,18 @@ The optional `targetDataPath` argument writes the payload of each successful cal

Most MCP tools return plain `content` blocks rather than A2UI resources, so a surface needs to pick the fields it wants out of the payload and write them to the data model. The v1 catalog declares these functions for that:

| Function | Arguments | Returns |
| :---------------- | :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `transformData` | `jmespath`, `data` | The result of evaluating the [JMESPath](https://jmespath.org/specification.html) expression against `data`, or `null` for missing fields. |
| `updateDataModel` | `updates` | Nothing. Writes each key-value pair in `updates` to the surface data model. |
| Function | Arguments | Returns |
| :---------------- | :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `transformData` | `jmespath`, `data` | The result of evaluating the [JMESPath](https://jmespath.org/specification.html) expression against `data`, or `null` for missing fields. |
| `updateDataModel` | `updates` | Nothing. Writes each key-value pair in `updates` to the surface data model. |
| `split` | `value`, `separator` | Substrings of `value` split by the literal `separator`, or its characters if `separator` is empty. |
| `regexCapture` | `value`, `pattern` | The capture groups of the first match of `pattern`, or `null` if there is no match. |

Every argument is required, and a `null` or missing argument is rejected. `transformData` implements standard JMESPath, with no extensions. `jmespath` is a `DynamicString`; `data` and `updates` are `DynamicValue`, so each can be a literal, a `@path` binding or a nested `@call`. 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).
Every argument is required, and a `null` or missing argument is rejected. `transformData` implements standard JMESPath, with no extensions. `jmespath`, `separator` and `pattern` are `DynamicString`; `data`, `updates` and `value` are `DynamicValue`, so each can be a literal, a `@path` binding or a nested `@call`. `split` and `regexCapture` accept a single string or an array of strings in `value`, applying the operation to each element of an array. 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).

`regexCapture` patterns use a portable subset that every renderer can implement: JavaScript regular expression syntax without lookahead or lookbehind assertions and without backreferences. Implementations also reject patterns that allow catastrophic backtracking. To test whether a string matches a pattern, use the basic catalog's `regex` function.

The set of data functions is closed, because each one has to be specified, tested and implemented in every renderer. The four cover the JSON and text output of most MCP servers: `split` and `regexCapture` exist because many servers return list data only as text, mostly as records of `Key: value` lines. These functions are mainly for general-purpose hosts (such as chat apps or playgrounds) where users configure which MCP servers to connect, so the host does not need custom parsing code for each tool. A host that needs another parser, or that only connects to a few servers it controls, can define its own function in a custom catalog and call it with that `catalogId`. The [TypeScript package README](../../typescript/catalogs/mcp/README.md#data-functions) explains the choice of each function and shows how to add one.

A surface is usually created on the basic catalog, so each call selects the MCP catalog with its own `catalogId`. The first button of [mcp-json-tool-result.json](v1/examples/mcp-json-tool-result.json) chains the functions this way:

Expand Down Expand Up @@ -80,7 +86,7 @@ A surface is usually created on the basic catalog, so each call selects the MCP
}
```

`transformData` reads the parsed `json` of the payload and builds the `{path: value}` object, and `updateDataModel` writes it. The second button only sets `targetDataPath: "/issueResponse"`, and its list binds to `/issueResponse/structuredContent/issues`. The functions do not evaluate `@path` or `@call` objects found inside tool output: a tool result is data, not an expression.
`transformData` reads the parsed `json` of the payload and builds the `{path: value}` object, and `updateDataModel` writes it. The second button only sets `targetDataPath: "/issueResponse"`, and its list binds to `/issueResponse/structuredContent/issues`. A tool that answers in plain text goes through `split` and `regexCapture`, as [mcp-text-tool-result.json](v1/examples/mcp-text-tool-result.json) does with search results: `transformData` reads `text`, `split` cuts it on `"\n\n"` into records, `regexCapture` with `^Title: (.*)\nURL: (.*)` reads each record, and `transformData` with `{"/results": [?@ != null].{title: [0], url: [1]}}` drops the records that did not match and names the fields. The functions do not evaluate `@path` or `@call` objects found inside tool output: a tool result is data, not an expression.

### McpApp component

Expand All @@ -102,6 +108,7 @@ The [v1/examples](v1/examples/) directory holds A2UI message sequences that vali

- [mcp-app-3d-geometry.json](v1/examples/mcp-app-3d-geometry.json) renders a 3D Holographic Geometry Studio `McpApp`: a 60fps canvas renderer that A2UI components alone cannot express. It showcases two-way `data.paths` binding (the sibling `TextField` preset and the app's camera telemetry), `ui/update-model-context` publishing the camera pose and preset to `/scene3d/modelContext`, `tools/call` (`capture_3d_snapshot`), and `ui/notifications/host-context-changed` (`containerDimensions`) driving a responsive canvas; `tests/examples/mcp_app_3d_geometry.test.ts` of the TypeScript package runs it end to end.
- [mcp-json-tool-result.json](v1/examples/mcp-json-tool-result.json) loads the open issues of a repository from a headless `list_issues` tool: one button chains `callMcpTool`, `transformData` and `updateDataModel` to fill a template list from the `json` of the payload, and a second button writes the payload to `targetDataPath` and binds a list to its `structuredContent`; `src/catalog.test.ts` of the TypeScript package runs both actions against a mock client.
- [mcp-text-tool-result.json](v1/examples/mcp-text-tool-result.json) searches the web through a headless `tavily-search` tool that answers in plain text: a button chains `callMcpTool`, `transformData`, `split`, `regexCapture` and `updateDataModel` to turn `Title:` and `URL:` records into rows; `src/catalog.test.ts` runs it against a mock client.

## Implementations

Expand Down
58 changes: 58 additions & 0 deletions catalogs/mcp/v1/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,58 @@
}
},
"required": ["@call", "args"]
},
"split": {
"type": "object",
"description": "Splits a string into an array of substrings using a literal 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 (Unicode code points)."
}
},
"required": ["value", "separator"],
"additionalProperties": false
}
},
"required": ["@call", "args"]
},
"regexCapture": {
"type": "object",
"description": "Extracts capture groups from the first 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": "A regular expression in the portable subset: JavaScript syntax without lookahead, lookbehind or backreferences. 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"]
}
},
"$defs": {
Expand All @@ -217,6 +269,12 @@
},
{
"$ref": "#/functions/updateDataModel"
},
{
"$ref": "#/functions/split"
},
{
"$ref": "#/functions/regexCapture"
}
]
}
Expand Down
137 changes: 137 additions & 0 deletions catalogs/mcp/v1/examples/mcp-text-tool-result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"name": "MCP text tool result",
"description": "Reads the plain text a headless MCP search tool returns and turns its records into data model rows. The tool answers with a header followed by one `Title:`, `URL:` and `Content:` record per result, separated by blank lines. The button chains the catalog's functions, each selecting the MCP catalog with its own catalogId: callMcpTool runs the search, transformData reads the `text` of the payload, split cuts it into records, regexCapture turns each record into a title and URL pair, a second transformData drops the records that did not match and names the fields, and updateDataModel writes them to /results.",
"messages": [
{
"version": "1.0",
"createSurface": {
"surfaceId": "gallery-mcp-text-tool-result",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"
}
},
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "gallery-mcp-text-tool-result",
"value": {
"query": "A2UI protocol",
"results": []
}
}
},
{
"version": "1.0",
"updateComponents": {
"surfaceId": "gallery-mcp-text-tool-result",
"components": [
{
"id": "root",
"component": "Column",
"children": ["heading", "query_field", "search_button", "result_list"],
"align": "stretch"
},
{
"id": "heading",
"component": "Text",
"text": "Web search"
},
{
"id": "query_field",
"component": "TextField",
"label": "Query",
"value": {
"@path": "/query"
}
},
{
"id": "search_button",
"component": "Button",
"child": "search_button_label",
"variant": "primary",
"action": {
"functionCall": {
"@call": "updateDataModel",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"updates": {
"@call": "transformData",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"jmespath": "{\"/results\": [?@ != null].{title: [0], url: [1]}}",
"data": {
"@call": "regexCapture",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"value": {
"@call": "split",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"value": {
"@call": "transformData",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"jmespath": "text",
"data": {
"@call": "callMcpTool",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"name": "tavily-search",
"arguments": {
"query": {
"@path": "/query"
}
}
}
}
}
},
"separator": "\n\n"
}
},
"pattern": "^Title: (.*)\nURL: (.*)"
}
}
}
}
}
}
}
Comment thread
josemontespg marked this conversation as resolved.
Resolved
},
{
"id": "search_button_label",
"component": "Text",
"text": "Search"
},
{
"id": "result_list",
"component": "Column",
"children": {
"componentId": "result_row",
"path": "/results"
}
},
{
"id": "result_row",
"component": "Column",
"children": ["result_title", "result_url"]
},
{
"id": "result_title",
"component": "Text",
"text": {
"@path": "title"
}
},
{
"id": "result_url",
"component": "Text",
"text": {
"@path": "url"
},
"variant": "caption"
}
]
}
}
]
}
2 changes: 1 addition & 1 deletion typescript/catalogs/mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- 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)
- `callMcpTool` returns a payload with `toolName`, `toolInput`, `toolResult`, `structuredContent`, `text` and `json`, and writes it to `targetDataPath` when set, where the latest call to a path wins. [#3728](https://github.com/a2ui-project/a2ui/pull/3728)
- The `transformData` (standard JMESPath) and `updateDataModel` data functions shape the `callMcpTool` payload into data model updates; their `*Api` and `*Implementation` are exported for hosts that register functions one by one. [#3573](https://github.com/a2ui-project/a2ui/pull/3573), [#3736](https://github.com/a2ui-project/a2ui/pull/3736)
- The `transformData` (standard JMESPath) and `updateDataModel` data functions shape the `callMcpTool` payload into data model updates; `split` and `regexCapture` turn text results into records, with patterns limited to a portable regular expression subset. Their `*Api` and `*Implementation` are exported for hosts that register functions one by one. [#3573](https://github.com/a2ui-project/a2ui/pull/3573), [#3736](https://github.com/a2ui-project/a2ui/pull/3736), [#3574](https://github.com/a2ui-project/a2ui/pull/3574)
- 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
Loading
Loading