{
static readonly tagName = 'flight-status-card';
protected override readonly api = FlightStatusCardApi;
override render() {
const rawFlight = this.controller?.props?.flightNumber;
const rawFare = this.controller?.props?.fareAmount;
const flightNumber = typeof rawFlight === 'string' ? rawFlight : '';
const fareAmount = typeof rawFare === 'number' ? rawFare : undefined;
return html`
Flight ${flightNumber}
${fareAmount !== undefined ? html`$${fareAmount.toFixed(2)}` : nothing}
`;
}
}
const FlightStatusCard = {
...FlightStatusCardApi,
tagName: FlightStatusCardElement.tagName,
element: FlightStatusCardElement,
} satisfies LitComponentApi;
export const travelCatalog = new Catalog(
'https://example.com/catalogs/travel/v1/catalog.json',
'1.0',
[FlightStatusCard],
BASIC_FUNCTIONS,
);
const processor = new MessageProcessor([travelCatalog]);
```
#### Angular
```typescript
import {Component, computed} from '@angular/core';
import {z} from 'zod';
import {
AngularCatalog,
BASIC_FUNCTIONS,
CatalogComponent,
createComponentImplementation,
provideA2UI,
} from '@a2ui/angular';
import {type ComponentApi} from '@a2ui/web_core';
import {DynamicNumberSchema, DynamicStringSchema} from '@a2ui/web_core/v1_0';
const FlightStatusCardApi = {
name: 'FlightStatusCard',
allowedParents: ['Column', 'List'],
schema: z.object({
flightNumber: DynamicStringSchema,
fareAmount: DynamicNumberSchema.optional(),
}),
} satisfies ComponentApi;
@Component({
selector: 'app-flight-status-card',
template: `
Flight {{ flightNumber() }}
@if (formattedFare(); as fare) {
\${{ fare }}
}
`,
})
export class FlightStatusCardComponent extends CatalogComponent {
readonly flightNumber = computed(() => {
const flight = this.props().flightNumber?.value();
return typeof flight === 'string' ? flight : '';
});
readonly formattedFare = computed(() => {
const fare = this.props().fareAmount?.value();
return typeof fare === 'number' ? fare.toFixed(2) : undefined;
});
}
export const travelCatalog = new AngularCatalog(
'https://example.com/catalogs/travel/v1/catalog.json',
'1.0',
[createComponentImplementation(FlightStatusCardApi, FlightStatusCardComponent)],
BASIC_FUNCTIONS,
);
export const appA2uiProviders = provideA2UI({catalogs: [travelCatalog]});
```
#### SwiftUI
```swift
import A2UICore
import A2UISwiftUI
import BasicCatalog
import JSONSchema
import SwiftUI
let flightStatusCard = ComponentImplementation(
name: "FlightStatusCard",
schema: try! Schema(
instance: """
{
"type": "object",
"properties": {
"component": { "const": "FlightStatusCard" },
"flightNumber": { "$ref": "common_types.json#/$defs/DynamicString" },
"fareAmount": { "$ref": "common_types.json#/$defs/DynamicNumber" }
},
"required": ["component", "flightNumber"]
}
""",
remoteSchemas: A2UICommonSchema.allSchemas
),
allowedParents: ["Column", "List"]
) { node in
HStack {
Image(systemName: "airplane.departure")
Text("Flight \(node.string(for: "flightNumber") ?? "")")
.font(.headline)
Spacer()
if let fare = node.double(for: "fareAmount") {
Text(String(format: "$%.2f", fare))
}
}
.padding()
}
let travelCatalog = Catalog(
id: "https://example.com/catalogs/travel/v1/catalog.json",
protocolVersion: "1.0",
components: [flightStatusCard],
functions: V10BasicFunctions.allFunctions
)
let processor = MessageProcessor(catalogs: [travelCatalog])
```
#### Jetpack Compose
```kotlin
import android.content.Context
import java.util.Locale
import androidx.a2ui.compose.runtime.A2uiComponentProperties
import androidx.a2ui.compose.runtime.A2uiComponentScope
import androidx.a2ui.compose.runtime.A2uiMessageProcessor
import androidx.a2ui.compose.runtime.A2uiProperty
import androidx.a2ui.compose.ui.A2uiCatalog
import androidx.a2ui.compose.ui.A2uiComponent
import androidx.a2ui.compose.ui.catalog.A2uiBasicCatalogV1
import androidx.a2ui.model.processor.A2uiMessageProcessor
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.material3.a2ui.catalog.MaterialA2uiBasicCatalogV1Defaults
import androidx.compose.material3.a2ui.catalog.materialA2uiBasicCatalogV1
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
private val FlightNumberProp =
A2uiProperty.dynamicString("flightNumber", required = true)
private val FareAmountProp =
A2uiProperty.dynamicNumber("fareAmount")
val FlightStatusCard: A2uiComponent =
object : A2uiComponent {
override val name = "FlightStatusCard"
override val description = "Shows a flight's number and fare."
override val properties = listOf(FlightNumberProp, FareAmountProp)
@Composable
override fun A2uiComponentScope.isReady(properties: A2uiComponentProperties): Boolean =
properties.bind(FlightNumberProp) != null
@Composable
override fun A2uiComponentScope.Content(
properties: A2uiComponentProperties,
modifier: Modifier,
) {
val flightNumber = properties.bind(FlightNumberProp) ?: return
val fare = properties.bind(FareAmountProp)?.toDouble()
Card(modifier = modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(text = "Flight $flightNumber")
if (fare != null) {
Text(text = String.format(Locale.US, "$%.2f", fare))
}
}
}
}
}
// androidx.a2ui implements v0.9.1, where each surface binds to a single catalogId.
fun createTravelProcessor(context: Context): A2uiMessageProcessor {
val basicCatalog =
materialA2uiBasicCatalogV1(
image = MaterialA2uiBasicCatalogV1Defaults.image { _, _, _, _, _ -> },
video = MaterialA2uiBasicCatalogV1Defaults.video { _, _, _ -> },
audioPlayer = MaterialA2uiBasicCatalogV1Defaults.audioPlayer { _, _, _, _ -> },
functions = A2uiBasicCatalogV1.Functions(context),
)
val travelCatalog =
A2uiCatalog(
catalogId = "https://example.com/catalogs/travel/v1/catalog.json",
components = basicCatalog.components + FlightStatusCard,
functions = basicCatalog.functions,
themeSchema = basicCatalog.themeSchema,
)
// Launch processor.collectMessages() in a background coroutine scope
// (such as viewModelScope) to process queued messages:
val processor = A2uiMessageProcessor(catalogs = listOf(travelCatalog))
return processor
}
```
## The basic catalog
A2UI publishes a basic catalog with 18 general-purpose components and 14 functions. The v1.0 schema lives at [`catalogs/basic/v1/catalog.json`](https://github.com/a2ui-project/a2ui/blob/main/catalogs/basic/v1/catalog.json), and the v0.9.1 schema lives at [`specification/v0_9_1/catalogs/basic/catalog.json`](https://github.com/a2ui-project/a2ui/blob/main/specification/v0_9_1/catalogs/basic/catalog.json).
| Category | Entries |
| :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Layout | `Row`, `Column`, `List`, `Card`, `Tabs`, `Modal`, `Divider` |
| Display and media | `Text`, `Image`, `Icon`, `Video`, `AudioPlayer` |
| Inputs | `Button`, `TextField`, `CheckBox`, `ChoicePicker`, `Slider`, `DateTimeInput` |
| Functions | `formatString`, `formatNumber`, `formatCurrency`, `formatDate`, `pluralize`, `required`, `regex`, `length`, `numeric`, `email`, `openUrl`, `and`, `or`, `not` |
You do not have to use the basic catalog. You can adopt it as is, restyle its components to match your design system, combine it with domain-specific catalogs on the same surface, or replace it altogether.
For an example of a domain catalog built on the basic catalog, see the experimental [Google Maps Platform Agentic UI toolkit](https://developers.google.com/maps/ai/agentic-ui-toolkit). Its catalog, `a2ui://maps-agentic-ui-catalog.json`, adds `GoogleMap` and `PlaceDetailsCompact` components to the basic catalog's components and functions. The toolkit ships renderers for web (Lit), Android, and iOS, so agents can show maps, routes, and place details next to standard layout widgets.
## Catalog negotiation and multi-catalog surfaces
When a session starts, the renderer sends `supportedCatalogIds` to list the catalogs it supports. When creating a surface, the agent picks from that list (or sends no UI if none match).
#### v1.0
Set `catalogId` in `createSurface` to choose the surface's default catalog, then add `@catalogId` on any component in `updateComponents` (or `catalogId` on any function call, `@call`) that comes from another supported catalog.
In this example, `salesDashboard` uses the basic catalog by default, and `chart` comes from a custom charts catalog:
```json
[
{
"version": "1.0",
"createSurface": {
"surfaceId": "salesDashboard",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"
}
},
{
"version": "1.0",
"updateComponents": {
"surfaceId": "salesDashboard",
"components": [
{"id": "root", "component": "Column", "children": ["title", "chart"]},
{"id": "title", "component": "Text", "text": "Quarterly sales"},
{
"id": "chart",
"component": "BarChart",
"@catalogId": "https://example.com/catalogs/charts/v1/catalog.json",
"data": {"@path": "/sales"}
}
]
}
}
]
```
Edge cases when createSurface.catalogId is omitted
`catalogId` in `createSurface` is optional. If a surface omits `createSurface.catalogId`, every component must set its own `@catalogId` and every function call its own `catalogId`. If an entry omits its catalog ID and the surface has no default `catalogId` either, the renderer rejects that component or function call rather than falling back to `supportedCatalogIds`. See [Mixable catalogs and component resolution logic](/specification/v1.0-a2ui/#mixable-catalogs-and-component-resolution-logic) for the full resolution rules.
#### v0.9.1
The agent selects a single `catalogId` in `createSurface`, locked for the lifetime of that surface:
```json
{
"version": "v0.9",
"createSurface": {
"surfaceId": "salesDashboard",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
}
```
---
# Data Model and Data Binding
> Learn how A2UI separates surface UI structure from reactive JSON state, enabling dynamic display bindings, two-way input bindings, dynamic list templates, and state synchronization back to the agent.
Canonical URL: https://a2ui.org/concepts/data-binding/
A2UI separates UI component structure from runtime data by giving every surface an isolated JSON data model and binding components to paths within that model.
Components reference locations inside the data model using [RFC 6901 JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901) (such as `/reservation/destination` or `/reservation/guestEmail`). When either the agent or the user updates a path in the data model, only the components bound to that path update.
The following diagram shows how a surface data model and component bindings produce a rendered UI. On the left, the surface data model stores reservation state in JSON, and component definitions bind to paths such as `/reservation/destination` and `/reservation/guestEmail`. On the right, an application window renders the resulting interface: a heading displaying the destination, an editable text field bound two-way to the guest email, and a confirmation button.
---
## Populating the data model
Each surface maintains its own JSON data model. In v1.0, you can provide an initial `dataModel` object inline when sending [`createSurface`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/CreateSurfaceMessage), or populate and update the data model at any time by sending [`updateDataModel`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateDataModelMessage) messages (in v0.9.1, data is always sent via [`updateDataModel`](/specification/v0.9.1/schemas/#server_to_client.json/$defs/UpdateDataModelMessage) after creating the surface).
When the renderer receives an `updateDataModel` message, it applies upsert semantics at the target JSON Pointer `path`. If the path does not exist yet, the renderer creates any missing intermediate objects and inserts the value; if the path already exists, the renderer replaces the value at that path. Omitting `path` (or setting it to `"/"`) replaces the entire root data model for the surface. To delete a key at `path`, set `"value": null` in v1.0, or omit the `value` property in v0.9.1.
#### v1.0
```json
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "booking",
"path": "/reservation/destination",
"value": "Kyoto, Japan"
}
}
```
#### v0.9.1
```json
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "booking",
"path": "/reservation/destination",
"value": "Kyoto, Japan"
}
}
```
Because `updateDataModel` is separate from component layout messages ([`updateComponents`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateComponentsMessage)), the data model can be updated directly by the language model during generation, or from deterministic code on the agent side (such as a tool handler or backend service injecting database records or API responses directly into the surface without passing them through the model).
---
## Binding to the data model
UI components can be bound to paths in the surface data model so they can display and update data. Component properties typed as dynamic values (such as [`DynamicString`](/specification/v1.0/schemas/#common_types.json/$defs/DynamicString), [`DynamicNumber`](/specification/v1.0/schemas/#common_types.json/$defs/DynamicNumber), [`DynamicBoolean`](/specification/v1.0/schemas/#common_types.json/$defs/DynamicBoolean), [`DynamicStringList`](/specification/v1.0/schemas/#common_types.json/$defs/DynamicStringList), or [`DynamicValue`](/specification/v1.0/schemas/#common_types.json/$defs/DynamicValue)) accept either a static literal value or a [`DataBinding`](/specification/v1.0/schemas/#common_types.json/$defs/DataBinding) object (`{"@path": "/path"}` in v1.0, or `{"path": "/path"}` in v0.9.1). Dynamic properties can also evaluate functions, which are covered in [Actions and Functions](/concepts/actions-and-functions/).
### Reading data as dynamic values
To display a value from the data model, bind a component's dynamic property to a JSON Pointer path. Whenever the value at that path is populated or updated, the component updates automatically:
#### v1.0
```json
{
"id": "destinationText",
"component": "Text",
"text": {"@path": "/reservation/destination"}
}
```
#### v0.9.1
```json
{
"id": "destinationText",
"component": "Text",
"text": {"path": "/reservation/destination"}
}
```
### Two-way binding to dynamic values
Interactive input components (such as `TextField`, `CheckBox`, or `Slider`) bind their `value` property to a path in the data model. This creates a two-way binding: the input displays the current value at that path, and as the user types or toggles the control, the renderer updates that path in the local data model immediately:
#### v1.0
```json
{
"id": "emailInput",
"component": "TextField",
"label": "Guest Email",
"value": {"@path": "/reservation/guestEmail"}
}
```
#### v0.9.1
```json
{
"id": "emailInput",
"component": "TextField",
"label": "Guest Email",
"value": {"path": "/reservation/guestEmail"}
}
```
### Referencing the data model in events
Component events ([`action.event`](/specification/v1.0/schemas/#common_types.json/$defs/Action)) can include data model references inside their `context` object. When the user triggers the event (for example, by clicking a button to submit a form), the renderer resolves each path against the local data model and sends the current values in the outbound [`action`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/action) message:
#### v1.0
```json
{
"id": "submitButton",
"component": "Button",
"child": "submitLabel",
"action": {
"event": {
"name": "submit_reservation",
"context": {
"email": {"@path": "/reservation/guestEmail"}
}
}
}
}
```
#### v0.9.1
```json
{
"id": "submitButton",
"component": "Button",
"child": "submitLabel",
"action": {
"event": {
"name": "submit_reservation",
"context": {
"email": {"path": "/reservation/guestEmail"}
}
}
}
}
```
### Displaying dynamic lists of data
Container components whose `children` property uses [`ChildList`](/specification/v1.0/schemas/#common_types.json/$defs/ChildList) (such as `Column`, `Row`, or `List`) can either take a static array of child component IDs (`["item1", "item2"]`) or generate children dynamically from a list in the data model using a template object with `path` and `componentId` (`ChildList` uses the property name `"path"` in both v1.0 and v0.9.1).
When a container binds to a list path (such as `"/guests"`), the renderer repeats the template component once for each item in the array and creates a local data scope for that item:
1. A **local data model path** omits the leading slash (such as `{"@path": "name"}` in v1.0, or `{"path": "name"}` in v0.9.1) and resolves relative to the current list item. For the first item in `"/guests"`, `"name"` resolves to `"/guests/0/name"`, and for the second item, it resolves to `"/guests/1/name"`.
2. A **root data model path** starts with a leading slash (such as `{"@path": "/reservation/destination"}`) and always resolves from the root of the surface data model, even inside a list template.
3. In v1.0, the **`@index` system function** (`{"@call": "@index"}`, with an optional `"args": {"offset": 1}`) returns the current item's 0-based index inside a list template.
The following example populates a `/guests` list in the data model and renders each guest's `name` using a `Column` template:
#### v1.0
```json
[
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "booking",
"path": "/guests",
"value": [{"name": "Alex Rivera"}, {"name": "Sam Taylor"}]
}
},
{
"version": "1.0",
"updateComponents": {
"surfaceId": "booking",
"components": [
{
"id": "root",
"component": "Column",
"children": {
"path": "/guests",
"componentId": "guestRow"
}
},
{
"id": "guestRow",
"component": "Text",
"text": {"@path": "name"}
}
]
}
}
]
```
#### v0.9.1
```json
[
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "booking",
"path": "/guests",
"value": [{"name": "Alex Rivera"}, {"name": "Sam Taylor"}]
}
},
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "booking",
"components": [
{
"id": "root",
"component": "Column",
"children": {
"path": "/guests",
"componentId": "guestRow"
}
},
{
"id": "guestRow",
"component": "Text",
"text": {"path": "name"}
}
]
}
}
]
```
The following diagram shows how the `/guests` array and the `guestRow` template expand into a rendered list UI. On the left, the `/guests` array holds two objects and `guestRow` binds its text to the local path `"name"`. On the right, an application window renders two list items resolved from `/guests/0/name` and `/guests/1/name`.
---
## Synchronizing state back to the agent
When a user edits an input component, two-way bindings update the surface data model locally on the client without streaming every keystroke over the network. To synchronize data model state back to the agent, especially values updated by input components, you can use three mechanisms depending on how the data will be processed.
### Events
Include data model references in `action.event.context` to send specific values (such as form fields) when the user triggers an [`action`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/action) event. When the user clicks the `submitButton` component shown earlier, the renderer resolves each bound path against the local data model and sends the resolved `action` message to the agent. This is best when you want to send data model state to an agent to reason over and process:
#### v1.0
```json
{
"version": "1.0",
"action": {
"name": "submit_reservation",
"surfaceId": "booking",
"sourceComponentId": "submitButton",
"timestamp": "2026-03-15T14:30:00Z",
"context": {
"email": "alex@example.com"
}
}
}
```
#### v0.9.1
```json
{
"version": "v0.9",
"action": {
"name": "submit_reservation",
"surfaceId": "booking",
"sourceComponentId": "submitButton",
"timestamp": "2026-03-15T14:30:00Z",
"context": {
"email": "alex@example.com"
}
}
}
```
### Agent functions
In v1.0, an interactive component such as a submit button can use `action.functionCall` to trigger an agent-side function (`callAgentFunction`) with arguments bound to paths in the data model. This is best when you want to send data model state directly to programmatic code on the agent side:
#### v1.0
```json
{
"id": "saveButton",
"component": "Button",
"child": "saveLabel",
"action": {
"functionCall": {
"@call": "saveReservation",
"args": {
"email": {"@path": "/reservation/guestEmail"}
}
}
}
}
```
#### v0.9.1
Agent-side functions were introduced in v1.0. In v0.9.1, pass bound data model paths in `action.event.context` instead:
```json
{
"id": "saveButton",
"component": "Button",
"child": "saveLabel",
"action": {
"event": {
"name": "save_reservation",
"context": {
"email": {"path": "/reservation/guestEmail"}
}
}
}
}
```
### `sendDataModel: true`
Set `"sendDataModel": true` in the [`createSurface`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/CreateSurfaceMessage) message to instruct the renderer to attach the full surface data model to the transport metadata of every outbound message (`a2uiRendererDataModel` in v1.0, or `a2uiClientDataModel` in v0.9.1, shaped as `{"version": "1.0", "surfaces": {"booking": {...}}}`). This is best when you want to synchronize complete surface state as context for the agent:
#### v1.0
```json
{
"version": "1.0",
"createSurface": {
"surfaceId": "booking",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json",
"sendDataModel": true
}
}
```
#### v0.9.1
```json
{
"version": "v0.9",
"createSurface": {
"surfaceId": "booking",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"sendDataModel": true
}
}
```
---
## Accessing the data model across SDKs
Each Core SDK provides a `dataModel` interface on the surface model for reading values by JSON Pointer path:
#### TypeScript
```typescript
const surface = processor.model.getSurface('booking');
const email = surface?.dataModel.get('/reservation/guestEmail');
```
#### Python
```python
surface = processor.model.get_surface("booking")
email = surface.data_model.get("/reservation/guestEmail") if surface else None
```
#### Dart
```dart
final surface = processor.model.getSurface('booking');
final email = surface?.dataModel.get('/reservation/guestEmail');
```
#### Swift
```swift
let surface = processor.surfaceGroupModel["booking"]
let email = surface?.dataModel.get("/reservation/guestEmail")
```
#### Kotlin
```kotlin
import androidx.a2ui.engine.model.A2uiCoreSurfaceModel
import androidx.a2ui.model.protocol.A2uiDataPath
// Accessing the data model directly requires the androidx.a2ui:a2ui-engine
// dependency (which provides A2uiCoreSurfaceModel and A2uiCoreDataModel).
// Surface instances emitted by A2uiMessageProcessor implement A2uiCoreSurfaceModel:
val surface =
processor.activeSurfaces.value.firstOrNull { it.id == "booking" } as? A2uiCoreSurfaceModel
val email = surface?.dataModel?.get(A2uiDataPath("/reservation/guestEmail"))
```
---
# Glossary
> Alphabetical reference of A2UI protocol, SDK, and generative UI terminology.
Canonical URL: https://a2ui.org/concepts/glossary/
A2UI (Agent to User Interface) uses specific terms for its wire protocol, client and server SDKs, and generative UI patterns, defined below in alphabetical order.
[A](#a) · [B](#b) · [C](#c) · [D](#d) · [E](#e) · [F](#f) · [G](#g) · [I](#i) · [M](#m) · [N](#n) · [P](#p) · [R](#r) · [S](#s)
## A
### A2UI
**Agent to User Interface**, an open-source generative UI framework (a platform-agnostic streaming protocol, native renderers, and agent SDKs) that lets [agents](#agent) generate stateful, native user interfaces across web, mobile, and desktop [renderers](#renderer) without sending executable code, HTML, or CSS over the wire.
See [Overview: How A2UI Works](/concepts/overview/) and the [v1.0 Protocol Specification](/specification/v1.0-a2ui/).
### A2UI message
A single JSON envelope exchanged between an [agent](#agent) and a [renderer](#renderer) over a streaming or request-response transport.
Messages flow in two directions: [`agent_to_renderer`](#agent_to_renderer) envelopes create or delete [surfaces](#surface), update [components](#component) and [data models](#data-model), invoke renderer functions, and return agent function results; [`renderer_to_agent`](#renderer_to_agent) envelopes dispatch user [events](#event), invoke agent functions, return renderer function results, and report validation or runtime errors. Because A2UI supports progressive delivery over streaming transports, a message stream may be in progress, completed, or interrupted if transport delivery stops early.
See [Messages](/concepts/messages/).
### A2UI tag
A delimiter tag in model-to-agent text output, such as `...` (used by the Express [inference format](#inference-format)) or `...` (used by the Direct JSON format), that marks the start and end of a structured UI block.
Because a single model response can interleave conversational prose with one or more UI blocks, the [Agent SDK](#agent-sdk) parser scans for A2UI tags to separate prose from raw UI blocks before compiling those blocks into [`agent_to_renderer`](#agent_to_renderer) messages.
See [Inference Formats](/concepts/inference-formats/).
### A2uiGenerator
The top-level coordinator class in an [Agent SDK](#agent-sdk) that holds the agent's supported `CatalogConfig` entries, optional prompt examples, and default [inference format](#inference-format) factory (`ExpressFormatFactory` by default).
For each session or request, calling `createProcessor` (in TypeScript and Dart) or `create_processor` (in Python) with the renderer's [capabilities](#capabilities) negotiates the active [catalogs](#catalog) and returns an `A2uiRequestProcessor` that generates system prompt instructions and parses and validates model responses against `RendererState`.
See [Agent SDK](/concepts/sdks/#agent-sdk) and [How to Integrate A2UI into an Agent](/guides/agent-integration/).
### Action
An interaction handler property on a UI [component](#component) (such as `Button.action`), defined by [`common_types.json#/$defs/Action`](/specification/v1.0/schemas/#common_types.json/$defs/Action) in v1.0 and [`common_types.json#/$defs/Action`](/specification/v0.9.1/schemas/#common_types.json/$defs/Action) in v0.9.1.
An `Action` is a `oneOf` union that specifies one of two behaviors when a user interacts with the component:
1. An [`event`](#event) (`{"event": {"name": "...", "context": {...}}}`), which causes the renderer to resolve any bound values and send an outbound [`action`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/action) message in [`renderer_to_agent`](#renderer_to_agent) to notify the agent of user intent.
2. A [`functionCall`](#function-call) (`{"functionCall": {"@call": "...", "args": {...}}}` in v1.0, or `"call"` in v0.9.1), which invokes a [function](#function) without dispatching an `action` event message (in v1.0, running locally on the renderer or routing to the agent via [`callAgentFunction`](#callagentfunction) when unimplemented locally).
See [Actions and Functions](/concepts/actions-and-functions/) and [Defining actions](/specification/v1.0-a2ui/#defining-actions) in the protocol specification.
### Agent
The application or service that emits [`agent_to_renderer`](#agent_to_renderer) messages to build and update UI [surfaces](#surface) on a [renderer](#renderer), and processes inbound [`renderer_to_agent`](#renderer_to_agent) events and remote function calls.
To decide what UI and data to emit, an agent typically calls a large language model (LLM), coordinates specialized subagents behind an orchestrator, or runs deterministic application logic (for example, in regulated workflows or automated tests). An agent can run on a server over transports such as AG-UI, Agent-to-Agent (A2A), Model Context Protocol (MCP), Server-Sent Events (SSE), or WebSockets, or inside the same client process as the renderer. Agents may be stateful (retaining negotiated [catalogs](#catalog) and `RendererState` across turns) or stateless (receiving renderer [capabilities](#capabilities) and surface [data model](#data-model) metadata on every request).
See [Overview: How A2UI Works](/concepts/overview/) and [How to Integrate A2UI into an Agent](/guides/agent-integration/).
### Agent SDK
A first-party agent-side library (published for TypeScript, Python, and Dart on top of each language's [Core SDK](#core-sdk)) that manages an [agent](#agent)'s interaction with language models and [renderers](#renderer).
An Agent SDK loads and prunes [catalogs](#catalog) via [catalog transformers](#catalog-transformer), negotiates renderer [capabilities](#capabilities) through [`A2uiGenerator`](#a2uigenerator), generates model skills and system prompt snippets, compiles [inference format](#inference-format) output into wire JSON, and validates payloads against `RendererState` so errors can be fed back to the model for self-correction.
See [Agent SDK](/concepts/sdks/#agent-sdk) for official package names and capabilities.
### agent_to_renderer
The v1.0 protocol [envelope schema](#envelope-schema) ([`agent_to_renderer.json`](/specification/v1.0/schemas/#agent_to_renderer.json)) for messages streamed or returned from an [agent](#agent) to a [renderer](#renderer). In v0.9.1, this envelope schema is named [`server_to_client.json`](/specification/v0.9.1/schemas/#server_to_client.json).
Every v1.0 `agent_to_renderer` message sets `"version": "1.0"` (v0.9.1 uses `"version": "v0.9"` or `"v0.9.1"`) alongside one of six message keys:
1. [`createSurface`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/CreateSurfaceMessage) initializes a new [surface](#surface) (and in v1.0 can embed initial `components` and `dataModel` inline).
2. [`updateComponents`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateComponentsMessage) adds or replaces [component](#component) definitions in a surface's flat component list.
3. [`updateDataModel`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateDataModelMessage) inserts, replaces, or deletes (`"value": null` in v1.0; omitted `value` in v0.9.1) data at a JSON Pointer path in a surface's [data model](#data-model).
4. [`deleteSurface`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/DeleteSurfaceMessage) removes a surface and its associated state.
5. [`callRendererFunction`](#callrendererfunction) (new in v1.0) invokes an authorized renderer-side [function](#function).
6. [`agentFunctionResponse`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/AgentFunctionResponseMessage) (new in v1.0) returns the `value` or `error` for a [`callAgentFunction`](#callagentfunction) request.
See [Messages](/concepts/messages/) and [Envelope message structure](/specification/v1.0-a2ui/#envelope-message-structure) in the protocol specification.
### Agent turn
The sequence of [`agent_to_renderer`](#agent_to_renderer) messages (and any accompanying conversational text) sent by an [agent](#agent) in response to a user prompt or [`Event`](#event) before the agent waits for the next user input. The end of an agent turn is signaled by the transport layer rather than by an A2UI message.
See [Messages](/concepts/messages/) and [Transport decoupling](/specification/v1.0-a2ui/#transport-decoupling) in the protocol specification.
## B
### Basic catalog
The optional, general-purpose reference [catalog](#catalog) maintained by the A2UI project (`https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json` in v1.0, [`basic_catalog.json`](/specification/v1.0-basic-catalog-implementation-guide/#basic_catalog.json); `https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json` in v0.9.1, [`basic_catalog.json`](/specification/v0.9.1-basic-catalog-implementation-guide/#basic_catalog.json)).
The Basic catalog defines 18 standard [components](#component) (`Row`, `Column`, `List`, `Card`, `Tabs`, `Modal`, `Divider`, `Text`, `Image`, `Icon`, `Video`, `AudioPlayer`, `Button`, `TextField`, `CheckBox`, `ChoicePicker`, `Slider`, and `DateTimeInput`) and 14 [functions](#function) (`formatString`, `formatNumber`, `formatCurrency`, `formatDate`, `pluralize`, `required`, `regex`, `length`, `numeric`, `email`, `openUrl`, `and`, `or`, and `not`). Official [framework adapters](#framework-adapter) ship with a built-in implementation that applications can restyle, combine with custom domain catalogs, or replace altogether.
See [Catalogs](/concepts/catalogs/#the-basic-catalog) and [Basic Component Catalog](/specification/v1.0-a2ui/#basic-component-catalog) in the protocol specification.
## C
### @call
The reserved v1.0 protocol directive key in a [`FunctionCall`](/specification/v1.0/schemas/#common_types.json/$defs/FunctionCall) object that names the catalog or system [function](#function) to invoke, for example `{"@call": "formatCurrency", "args": {"value": {"@path": "/price"}, "currency": "USD"}}`.
In v0.9.1, `FunctionCall` uses `"call"` without the `@` prefix ([`common_types.json#/$defs/FunctionCall`](/specification/v0.9.1/schemas/#common_types.json/$defs/FunctionCall)). Reserving the `@` prefix in v1.0 keeps plain JSON objects that have a `"call"` property from being treated as function calls; literal keys beginning with `@` inside `DynamicValue` objects are escaped by doubling the prefix (`"@@call"`).
See [Function call](#function-call), [Actions and Functions](/concepts/actions-and-functions/), and [Functions](/specification/v1.0-a2ui/#functions) in the protocol specification.
### callAgentFunction
A v1.0 [`renderer_to_agent`](#renderer_to_agent) message ([`renderer_to_agent.json#/properties/callAgentFunction`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/callAgentFunction)) sent by a [renderer](#renderer) to execute an agent-side [function](#function) remotely, such as checking live inventory or verifying an identifier.
When the renderer resolves a [`FunctionCall`](#function-call)'s [catalog](#catalog) and finds no local implementation (provided the function is not an `@`-prefixed system function, is not declared `allowedCallers: "agentOnly"`, and passes any `requiresUserActivation` and declared argument schema checks), it sends `callAgentFunction` with `surfaceId`, a unique `functionCallId`, and `callFunction` (`@call`, the resolved `catalogId`, and evaluated `args`). The [agent](#agent) runs the function and replies with an [`agentFunctionResponse`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/AgentFunctionResponseMessage) carrying the same `functionCallId` and either `value` or `error`. Not present in v0.9.1, where functions run only on the renderer.
See [Actions and Functions](/concepts/actions-and-functions/) and [`callAgentFunction`](/specification/v1.0-a2ui/#callagentfunction) in the protocol specification.
### callRendererFunction
A v1.0 [`agent_to_renderer`](#agent_to_renderer) message ([`agent_to_renderer.json#/$defs/CallRendererFunctionMessage`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/CallRendererFunctionMessage)) sent by an [agent](#agent) to execute a [function](#function) registered on the [renderer](#renderer), such as reading client state or triggering an authorized client operation.
The message carries a unique `functionCallId`, an optional `surfaceId` whose [data model](#data-model) provides the evaluation context (defaulting to an isolated empty root model `{}` when omitted), and `callFunction` with `@call`, a required `catalogId`, and optional `args`. Only functions declared with `allowedCallers` set to `"agentOnly"` or `"rendererOrAgent"` (and not requiring user activation) may be called by the agent. After executing or rejecting a well-formed call, the renderer returns a [`rendererFunctionResponse`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/rendererFunctionResponse) with the matching `functionCallId` and either `value` or `error` (malformed envelopes report a top-level [`error`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/error) message carrying `functionCallId`). Not present in v0.9.1.
See [Actions and Functions](/concepts/actions-and-functions/) and [`callRendererFunction`](/specification/v1.0-a2ui/#callrendererfunction) in the protocol specification.
### Capabilities
Metadata payloads exchanged via transport handshakes or per-message transport metadata (rather than as [A2UI messages](#a2ui-message)) that advertise which [catalogs](#catalog) each side supports:
1. Renderer capabilities ([`renderer_capabilities.json`](/specification/v1.0/schemas/#renderer_capabilities.json), sent under the `a2uiRendererCapabilities` metadata key in v1.0; [`client_capabilities.json`](/specification/v0.9.1/schemas/#client_capabilities.json) under `a2uiClientCapabilities` in v0.9.1) declare `supportedCatalogIds` and optional `inlineCatalogs` nested under the protocol version key (`"1.0"` in v1.0, `"v0.9"` in v0.9.1).
2. Agent capabilities ([`agent_capabilities.json`](/specification/v1.0/schemas/#agent_capabilities.json) in v1.0; [`server_capabilities.json`](/specification/v0.9.1/schemas/#server_capabilities.json) in v0.9.1) declare the agent's `supportedCatalogIds` and `acceptsInlineCatalogs` flag.
See [Overview: How A2UI Works](/concepts/overview/) and [Capabilities and metadata](/specification/v1.0-a2ui/#capabilities-and-metadata) in the protocol specification.
### Catalog
A versioned JSON Schema document ([`catalog_definition.json`](/specification/v1.0/schemas/#catalog_definition.json)) identified by a unique `catalogId` string (conventionally formatted as a URI) that defines the UI [components](#component), [functions](#function), and Markdown `instructions` shared between an [agent](#agent) and a [renderer](#renderer).
A catalog is an explicit allowlist: the agent uses it to construct model prompts and validate generated messages, while the renderer binds each declared component to a native UI widget and each local function to client code. Catalogs can define low-level primitives (such as `Row`, `Text`, and `Button` in the [Basic catalog](#basic-catalog)) or domain-specific components (such as `FlightSelector` or `HotelCheckout`). In v1.0, a single [surface](#surface) can mix components and functions from multiple catalogs by setting `catalogId` on individual entries.
See [Catalogs](/concepts/catalogs/), [How to Define a Catalog](/guides/defining-a-catalog/), and [The component catalog](/specification/v1.0-a2ui/#the-component-catalog) in the protocol specification.
### Catalog transformer
An [Agent SDK](#agent-sdk) interface (`CatalogTransformer`) that filters or adapts a [catalog](#catalog) before prompt instructions are generated and validation schemas are compiled.
Pruning unused components and functions lowers prompt token consumption and restricts what UI capabilities a model can emit for a specific task or user role. Built-in implementations in the TypeScript, Python, and Dart Agent SDKs include `ComponentPruningTransformer` (which keeps an allowlist of component names) and `FunctionPruningTransformer` (which keeps an allowlist of function names). For server-side composite component expansion, see [`MacroExpander`](#macroexpander).
See [Agent SDK](/concepts/sdks/#agent-sdk).
### CheckRule
A client-side validation rule ([`common_types.json#/$defs/CheckRule`](/specification/v1.0/schemas/#common_types.json/$defs/CheckRule) in v1.0; [`common_types.json#/$defs/CheckRule`](/specification/v0.9.1/schemas/#common_types.json/$defs/CheckRule) in v0.9.1) attached to input and interactive [components](#component) that include [`Checkable`](/specification/v1.0/schemas/#common_types.json/$defs/Checkable) (`@checks` in v1.0, `checks` in v0.9.1).
In v1.0, each `CheckRule` pairs a `condition` (a [`DataBinding`](#data-binding) or [`FunctionCall`](#function-call) evaluating to a [`ValidationResult`](/specification/v1.0/schemas/#catalog_definition.json/$defs/ValidationResult) object `{valid, code, message, severity}` or a boolean) with an optional fallback `message`; in v0.9.1, `condition` is a `DynamicBoolean` and `message` is required. Checks that fail with `"error"` severity (the default, and what a `false` boolean coerces to) mark the component invalid and disable controls such as `Button`, while `"warning"` and `"info"` results display non-blocking feedback.
See [Actions and Functions](/concepts/actions-and-functions/) and [Component Validation & Check Rules](/specification/v1.0-a2ui/#component-validation--check-rules) in the protocol specification.
### ChildList
The schema type ([`common_types.json#/$defs/ChildList`](/specification/v1.0/schemas/#common_types.json/$defs/ChildList) in v1.0; [`common_types.json#/$defs/ChildList`](/specification/v0.9.1/schemas/#common_types.json/$defs/ChildList) in v0.9.1) used by container [components](#component) (such as `Column`, `Row`, and `List`) to hold multiple child components in a [surface](#surface)'s flat component list.
A `ChildList` property accepts one of two forms:
1. A static array of `ComponentId` strings (for example, `["header", "body", "footer"]`).
2. A collection template object `{"path": "/items", "componentId": "itemCard"}` that instantiates `componentId` once for each element of the array at `path` in the [data model](#data-model), establishing a collection scope for relative [`@path`](#path) bindings and [`@index`](#index) calls. Template objects use the plain property name `"path"` (not `"@path"`) in both v1.0 and v0.9.1.
See [Data Model and Data Binding](/concepts/data-binding/#displaying-dynamic-lists-of-data) and [Collection scopes (relative paths)](/specification/v1.0-a2ui/#collection-scopes-relative-paths) in the protocol specification.
### Component
A declarative UI element defined in a [catalog](#catalog) (such as `Card`, `Button`, `TextField`, or a domain-specific `FlightStatusCard`) and rendered as a native widget by a client [framework adapter](#framework-adapter).
Within a [surface](#surface), component instances are sent in `createSurface` or `updateComponents` as a flat adjacency list rather than a nested tree: each entry has a unique `id` (following Unicode UAX #31 identifier rules in v1.0), a `component` type name, optional `@catalogId`, `@accessibility` attributes, and `@metadata.extensions` (bare `accessibility` in v0.9.1), and references children by ID (`ComponentId` or [`ChildList`](#childlist)). Every rendered surface requires one component with `"id": "root"`, which mounts inside the reserved immutable `Surface` container ([`common_types.json#/$defs/Surface`](/specification/v1.0/schemas/#common_types.json/$defs/Surface)).
See [Catalogs](/concepts/catalogs/) and [Component model](/specification/v1.0-a2ui/#component-model) in the protocol specification.
### Core SDK
The UI-framework-agnostic foundational library in each language, shared by both [Agent SDKs](#agent-sdk) and client [framework adapters](#framework-adapter).
The Core SDK implements protocol message models, `Catalog` schemas, reactive surface and [data model](#data-model) state containers (`SurfaceGroupModel`, `SurfaceModel`, `ComponentModel`, and `DataModel`, named `SurfaceViewModel` in Swift and `A2uiCoreSurfaceGroupModel`, `A2uiCoreSurfaceModel`, `A2uiCoreComponentRegistry`, and `A2uiCoreDataModel` in Kotlin, with `A2uiComponentModel` in `compose-runtime`), JSON Pointer resolution, [`@path`](#path) and [`@call`](#call) expression evaluation, [`MessageProcessor`](#messageprocessor), and payload validation.
See [Core SDK](/concepts/sdks/#core-sdk) for official package names across languages.
## D
### Data binding
Connecting a [component](#component)'s dynamic property (`DynamicString`, `DynamicNumber`, `DynamicBoolean`, `DynamicStringList`, or `DynamicValue` in [`common_types.json`](/specification/v1.0/schemas/#common_types.json)) to a path in the surface's [data model](#data-model) via a [`DataBinding`](/specification/v1.0/schemas/#common_types.json/$defs/DataBinding) object (`{"@path": "..."}` in v1.0, or `{"path": "..."}` in v0.9.1) instead of a literal value.
Display components (such as `Text`) use one-way reactive bindings that update whenever the bound path changes. Input components (such as `TextField`, `CheckBox`, and `Slider`) establish local two-way bindings that read the current value at the bound path and write user edits back to the local data model immediately without per-keystroke network traffic.
See [`@path`](#path), [Data Model and Data Binding](/concepts/data-binding/), and [Data model representation: binding, scope](/specification/v1.0-a2ui/#data-model-representation-binding-scope) in the protocol specification.
### Data model
An isolated, reactive JSON state object maintained per [surface](#surface) on the [renderer](#renderer) and shared with the [agent](#agent) that owns the surface.
The agent populates or mutates the data model inline in `createSurface` (v1.0) or incrementally via [`updateDataModel`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateDataModelMessage) using [RFC 6901 JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901). Local user edits remain on the renderer until sent back to the agent selectively in [`Event`](#event) `context` bindings, as resolved arguments to agent-routed function calls ([`callAgentFunction`](#callagentfunction) in v1.0), or in full via transport metadata (`a2uiRendererDataModel` matching [`renderer_data_model.json`](/specification/v1.0/schemas/#renderer_data_model.json) in v1.0; `a2uiClientDataModel` matching [`client_data_model.json`](/specification/v0.9.1/schemas/#client_data_model.json) in v0.9.1) when `createSurface` sets `"sendDataModel": true`.
See [Data Model and Data Binding](/concepts/data-binding/) and [Data model updates: synchronization and convergence](/specification/v1.0-a2ui/#data-model-updates-synchronization-and-convergence) in the protocol specification.
## E
### Envelope schema
A top-level JSON Schema that defines the outer wire structure (`"version"` plus a single message type key) for an [A2UI message](#a2ui-message) exchanged between an [agent](#agent) and a [renderer](#renderer), independent of which [catalog](#catalog) defines the UI [components](#component) and [functions](#function):
1. [`agent_to_renderer.json`](/specification/v1.0/schemas/#agent_to_renderer.json) ([`agent_to_renderer`](#agent_to_renderer)) validates v1.0 messages from agent to renderer (`"version": "1.0"`).
2. [`renderer_to_agent.json`](/specification/v1.0/schemas/#renderer_to_agent.json) ([`renderer_to_agent`](#renderer_to_agent)) validates v1.0 messages from renderer to agent (`"version": "1.0"`).
3. [`server_to_client.json`](/specification/v0.9.1/schemas/#server_to_client.json) validates v0.9.1 (and v0.8) messages from server (agent) to client (renderer), renamed to `agent_to_renderer.json` in v1.0.
4. [`client_to_server.json`](/specification/v0.9.1/schemas/#client_to_server.json) validates v0.9.1 (and v0.8) messages from client (renderer) to server (agent), renamed to `renderer_to_agent.json` in v1.0.
Each envelope schema also has companion JSON array (`*_list.json`) and object-wrapped array (`*_list_wrapper.json`) schemas for batched message delivery.
See [Messages](/concepts/messages/) and [Envelope message structure](/specification/v1.0-a2ui/#envelope-message-structure) in the protocol specification.
### Event
The agent-bound variant of a component [`Action`](#action) (`action.event` in [`common_types.json#/$defs/Action`](/specification/v1.0/schemas/#common_types.json/$defs/Action)) that notifies the [agent](#agent) of user intent, such as clicking a submit button or choosing an item.
An `event` object declares a required `name`, an optional `context` map of literal values or dynamic bindings, and (in v1.0) an optional `userMessage` (`DynamicString`). When triggered by the user (provided no component [`CheckRule`](#checkrule) fails with `"error"` severity), the [renderer](#renderer) resolves every binding in `context` and `userMessage` against the local [data model](#data-model) and emits an [`action`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/action) message in [`renderer_to_agent`](#renderer_to_agent) containing `name`, `surfaceId`, `sourceComponentId`, ISO 8601 `timestamp`, resolved `context`, and (in v1.0) optional `userMessage` and `metadata.extensions`.
See [Actions and Functions](/concepts/actions-and-functions/) and [`action`](/specification/v1.0-a2ui/#action) in the protocol specification.
## F
### Framework adapter
A UI-framework-specific client library (also called a Renderer SDK), built on top of a [Core SDK](#core-sdk), that renders active surface state from [`MessageProcessor`](#messageprocessor) using native widgets in a target framework such as Flutter, React, Lit Web Components, Angular, SwiftUI, or Jetpack Compose.
Framework adapters include a built-in implementation of the [Basic catalog](#basic-catalog), map A2UI `AccessibilityAttributes` (`label`, `description`, `live`, `hidden`) to platform accessibility APIs, and let teams register custom [catalog](#catalog) component views and local [functions](#function) while the adapter handles reactive state updates.
See [Renderer SDK](/concepts/sdks/#renderer-sdk) for official package names and [How to Integrate A2UI into a Client](/guides/client-integration/).
### Function
A named, typed operation declared in a [catalog](#catalog)'s `functions` map ([`catalog_definition.json#/$defs/FunctionDefinition`](/specification/v1.0/schemas/#catalog_definition.json/$defs/FunctionDefinition)) with an argument schema (`args`) and `returnType` (`"string"`, `"number"`, `"boolean"`, `"array"`, `"object"`, `"validationResult"`, `"any"`, or `"void"`). Functions execute logic without sending raw code between the [agent](#agent) and [renderer](#renderer).
In v1.0, whether a function call runs locally or on the agent depends on whether the renderer registers a local implementation, while `allowedCallers` (`"rendererOnly"` by default, `"agentOnly"`, or `"rendererOrAgent"`) and `requiresUserActivation` govern which side and gesture context may invoke it:
1. Renderer functions have a local implementation on the renderer and execute with zero network round-trips for tasks such as string and number formatting, input validation, boolean combinators, or user-gesture actions like `openUrl` (`requiresUserActivation: true`, which requires `"rendererOnly"`). Functions declared with `allowedCallers` set to `"agentOnly"` or `"rendererOrAgent"` (without `requiresUserActivation`) can also be invoked remotely by the agent via [`callRendererFunction`](#callrendererfunction); `"agentOnly"` functions still run on the renderer and cannot be bound to component properties or actions.
2. Agent functions (new in v1.0) have no local implementation on the renderer and execute on the agent for server-side tasks such as checking inventory or verifying credentials; when the renderer resolves a [`FunctionCall`](#function-call)'s catalog and finds no local implementation (whether or not the catalog declares the function, and provided the call is not `@`-prefixed, declared `"agentOnly"`, or blocked by `requiresUserActivation`), the renderer dispatches [`callAgentFunction`](#callagentfunction) and awaits `agentFunctionResponse`. (In v0.9.1, functions execute only on the renderer.)
An A2UI function is distinct from a language model tool:
| Aspect | A2UI function | LLM tool |
| :---------- | :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| Executor | [Renderer](#renderer) (renderer functions) or [agent](#agent) (agent functions). | Model host or agent environment outside the UI. |
| Timing | During UI rendering, on user interaction, or as an explicit remote call over the protocol. | During model generation before or while producing a response. |
| Purpose | UI formatting, input validation, client-side effects, or UI-triggered server lookups. | Model reasoning, web search, database queries, or backend operations. |
| Declaration | Declared in an A2UI [catalog](#catalog) with typed `args`, `returnType`, and caller rules. | Declared in a tool definition passed to the language model. |
See [Actions and Functions](/concepts/actions-and-functions/) and [Functions in A2UI Content Execution](/specification/v1.0-a2ui/#functions-in-a2ui-content-execution) in the protocol specification.
### Function call
An invocation of a catalog or system [function](#function) represented as a [`FunctionCall`](/specification/v1.0/schemas/#common_types.json/$defs/FunctionCall) object with [`@call`](#call) in v1.0 (`"call"` in v0.9.1), an optional `catalogId` override, and optional `args`.
Function calls occur in four places across the protocol:
1. Dynamic component property bindings (`DynamicString`, `DynamicNumber`, `DynamicBoolean`, `DynamicStringList`, and `DynamicValue`), re-evaluated reactively when bound [data model](#data-model) paths change.
2. Validation rules ([`CheckRule`](#checkrule) `@checks[].condition` in v1.0, `checks[].condition` in v0.9.1), returning a `ValidationResult` or boolean to validate inputs and gate controls.
3. Component [`Action`](#action) handlers (`action.functionCall`), triggered by a user gesture.
4. Explicit remote procedure call envelopes ([`callRendererFunction`](#callrendererfunction) and [`callAgentFunction`](#callagentfunction) in v1.0).
See [Actions and Functions](/concepts/actions-and-functions/).
## G
### Generative UI patterns
Common application layouts for presenting agent-generated [surfaces](#surface) (none of which are required by the A2UI protocol):
1. Chat: Generated surfaces appear in chronological order inside a vertically scrollable conversation view alongside text messages.
2. Canvas: A shared workspace where the user and [agent](#agent) iteratively inspect and update one or more persistent surfaces across turns.
3. Dashboard: Generated surfaces are arranged by topic or role rather than time and remain pinned in fixed regions of the screen.
4. Wizard: Generated surfaces appear step by step to gather structured input for a multi-stage task.
See [Overview: How A2UI Works](/concepts/overview/) and [How to Integrate A2UI into a Client](/guides/client-integration/).
## I
### @index
The reserved v1.0 universal system [function](#function) ([`common_types.json#/$defs/IndexSystemFunction`](/specification/v1.0/schemas/#common_types.json/$defs/IndexSystemFunction)) that returns the 0-based index of the current item when rendering a [`ChildList`](#childlist) collection template, called as `{"@call": "@index"}` or `{"@call": "@index", "args": {"offset": 1}}` (or `${@index(offset: 1)}` inside `formatString`).
In nested collection templates, `@index` resolves to the innermost enclosing template's index; calling `@index` outside a list template fails evaluation (reporting `EXPRESSION_ERROR` on dynamic bindings or `EXECUTION_ERROR` in [`callRendererFunction`](#callrendererfunction)).
See [Data Model and Data Binding](/concepts/data-binding/#displaying-dynamic-lists-of-data) and [The `@index` function](/specification/v1.0-a2ui/#the-index-function) in the protocol specification.
### Inference format
The structured notation in which a language model writes UI blocks inside [A2UI tags](#a2ui-tag) before the [Agent SDK](#agent-sdk) compiles them into [`agent_to_renderer`](#agent_to_renderer) wire JSON.
Because inference formats are decoupled from the wire protocol, an agent can switch formats on [`A2uiGenerator`](#a2uigenerator) without modifying client renderers. Supported formats include Express (`ExpressFormatFactory`, the default compact positional DSL that cuts output tokens and decode latency), Direct JSON (`DirectJsonFormatFactory`, raw JSON message arrays with incremental stream healing), and experimental Python-only formats (Atom and Elemental).
When reading a model response (`parseResponse` / `parse_response` or streaming `openStream` / `open_stream`), the Agent SDK parser runs three stages:
1. Tag unwrapping (`unwrap`): Scans model output for opening and closing A2UI tags to separate conversational prose from raw UI blocks.
2. Compilation (`compile`): Translates each raw block from format notation into `agent_to_renderer` messages without checking catalogs or `RendererState`.
3. Validation and state tracking: `parseResponse` / `parse_response` and `openStream` / `open_stream` validate compiled messages against the active [catalogs](#catalog) and update `RendererState`.
See [Inference Formats](/concepts/inference-formats/).
## M
### MacroExpander
An asymmetric catalog and message transformer in the Python [Agent SDK](#agent-sdk) (`a2ui.transformers.macros`) that lets developers define coarse-grained composite components on the server with the `@macro` decorator and lower them into standard [catalog](#catalog) primitives before wire transmission.
`MacroExpander.transform_to_inference_catalog` augments a base catalog with component schemas synthesized from `@macro` function type annotations and docstrings for model prompting, and `MacroExpander.transform_to_transport` expands macro components emitted by the model into primitive `createSurface` and `updateComponents` subtrees so client [renderers](#renderer) need no custom widget code.
See [Using Macros](/guides/macros/) and [Agent SDK](/concepts/sdks/#agent-sdk).
### MessageProcessor
The stateful controller class in each language's [Core SDK](#core-sdk) (`A2uiMessageProcessor` in Kotlin) that processes incoming [`agent_to_renderer`](#agent_to_renderer) (and v0.9.1 `server_to_client`) message streams.
`MessageProcessor` validates incoming payloads against registered [catalogs](#catalog), mutates reactive surface and [data model](#data-model) state containers (`SurfaceGroupModel`, `SurfaceModel`, `ComponentModel`, and `DataModel`, named `SurfaceViewModel` in Swift and `A2uiCoreSurfaceGroupModel`, `A2uiCoreSurfaceModel`, `A2uiCoreComponentRegistry`, and `A2uiCoreDataModel` in Kotlin, with `A2uiComponentModel` in `compose-runtime`), resolves JSON Pointers and [`@call`](#call) expressions, enforces function caller and user-activation rules, emits outbound [`renderer_to_agent`](#renderer_to_agent) messages (`action`, `callAgentFunction`, `rendererFunctionResponse`, and `error`), and builds renderer [capabilities](#capabilities) and `a2uiRendererDataModel` metadata snapshots.
See [Core SDK](/concepts/sdks/#core-sdk) and [How to Integrate A2UI into a Client](/guides/client-integration/).
## N
### NoAI information
Data categorized by an application as inaccessible to AI models and [agents](#agent), such as payment card details, credentials, or regulated personal records.
Which fields count as NoAI information depends on the product and workflow (for example, medical history may be excluded from AI context in a billing app while required in a clinical diagnostic tool). In generative UI interfaces, keeping NoAI inputs outside agent-bound [`Event`](#event) `context` maps, outside arguments to function calls that can route to the agent via [`callAgentFunction`](#callagentfunction), and off `"sendDataModel": true` surfaces (while visually distinguishing AI-connected inputs from private inputs) lets end users see which data can reach a model.
See [Synchronizing state back to the agent](/concepts/data-binding/#synchronizing-state-back-to-the-agent) in Data Model and Data Binding.
## P
### @path
The reserved v1.0 protocol directive key in a [`DataBinding`](/specification/v1.0/schemas/#common_types.json/$defs/DataBinding) object that points to a value in a surface's [data model](#data-model) using an [RFC 6901 JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901), such as `{"@path": "/reservation/guestEmail"}`.
A path starting with `/` resolves from the root of the surface data model, while a path without a leading slash (such as `{"@path": "guestName"}`) resolves relative to the current item in an enclosing [`ChildList`](#childlist) collection template. In v0.9.1, `DataBinding` uses `"path"` without the `@` prefix ([`common_types.json#/$defs/DataBinding`](/specification/v0.9.1/schemas/#common_types.json/$defs/DataBinding)). Even in v1.0, `updateDataModel.path` and `ChildList` template `path` use plain `"path"` because they are fixed schema fields rather than dynamic value unions.
See [Data binding](#data-binding), [Data Model and Data Binding](/concepts/data-binding/), and [Path resolution & scope](/specification/v1.0-a2ui/#path-resolution--scope) in the protocol specification.
## R
### Renderer
The client-side runtime inside a host application that advertises supported [catalogs](#catalog) via [capabilities](#capabilities), applies [`agent_to_renderer`](#agent_to_renderer) messages through [`MessageProcessor`](#messageprocessor), renders [surfaces](#surface) as native widgets via a [framework adapter](#framework-adapter), maintains per-surface [data models](#data-model), runs local [functions](#function), and sends [`renderer_to_agent`](#renderer_to_agent) messages back to the [agent](#agent).
See [Renderer SDK](/concepts/sdks/#renderer-sdk) and [How to Integrate A2UI into a Client](/guides/client-integration/).
### renderer_to_agent
The v1.0 protocol [envelope schema](#envelope-schema) ([`renderer_to_agent.json`](/specification/v1.0/schemas/#renderer_to_agent.json)) for messages sent from a [renderer](#renderer) to an [agent](#agent). In v0.9.1, this envelope schema is named [`client_to_server.json`](/specification/v0.9.1/schemas/#client_to_server.json).
Every v1.0 `renderer_to_agent` message sets `"version": "1.0"` (v0.9.1 uses `"version": "v0.9"` or `"v0.9.1"`) alongside one of four message keys:
1. [`action`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/action) dispatches a user-triggered [`Event`](#event) with its resolved `context`.
2. [`callAgentFunction`](#callagentfunction) (new in v1.0) requests remote execution of an agent-side [function](#function).
3. [`rendererFunctionResponse`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/rendererFunctionResponse) (new in v1.0) returns the `value` or `error` for an agent-initiated [`callRendererFunction`](#callrendererfunction) request.
4. [`error`](/specification/v1.0/schemas/#renderer_to_agent.json/properties/error) reports validation errors (`VALIDATION_FAILED`, `UNALLOWED_PARENT`, `UNALLOWED_CHILD`) or runtime faults (`EXPRESSION_ERROR`, `EXECUTION_ERROR`, `INTEGRITY_ERROR`, `INVALID_FUNCTION_CALL`), allowing the agent to prompt the model for self-correction.
See [Messages](/concepts/messages/) and [Renderer-to-agent event messages](/specification/v1.0-a2ui/#renderer-to-agent-event-messages) in the protocol specification.
## S
### Surface
An isolated, non-nesting UI region created with a globally unique `surfaceId` via [`createSurface`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/CreateSurfaceMessage), populated with a flat adjacency list of [components](#component) rooted at `"id": "root"`, backed by its own [data model](#data-model), and removed via [`deleteSurface`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/DeleteSurfaceMessage).
Creating a surface implicitly instantiates the reserved immutable `Surface` container ([`common_types.json#/$defs/Surface`](/specification/v1.0/schemas/#common_types.json/$defs/Surface)) with `"child": "root"`. Surfaces cannot nest inside one another, though a host application can display multiple surfaces at the same time (such as separate cards in a chat timeline or panels in a dashboard).
See [Overview: How A2UI Works](/concepts/overview/), [Messages](/concepts/messages/), and [`createSurface`](/specification/v1.0-a2ui/#createsurface) in the protocol specification.
---
# Inference Formats
> How A2UI decouples the wire protocol from model generation with Express, Direct JSON, and custom inference formats.
Canonical URL: https://a2ui.org/concepts/inference-formats/
An inference format defines the structured notation that a language model reads in its system prompt and writes in its response before the server compiles that output into standard A2UI [wire messages](/concepts/messages/). Because compilation happens on the server, every client [renderer](/concepts/sdks/#renderer-sdk) receives the same A2UI JSON over the wire regardless of how the model produced it.
In the diagram below, `A2uiRequestProcessor` coordinates three server-side stages. First, `PromptGenerator` turns the negotiated [catalog](/concepts/catalogs/) schemas, `instructions`, and few-shot `examples` into a format-specific prompt snippet. Next, the model writes UI blocks inside sentinel tags (`...` for Express or `...` for Direct JSON) alongside conversational prose. Finally, `Parser` or `ResponseStream` heals streaming chunks, compiles each block into wire JSON, and validates every message against `RendererState`.
## Wire protocol vs. generation formats
Standard JSON is well suited as a wire protocol between an agent server and a client renderer: it is self-describing, language-independent, and validated directly by JSON Schema. Asking a language model to emit raw wire JSON token by token, however, carries avoidable costs:
1. Repeated keys (`"id"`, `"component"`, `"@path"`) and hand-written component identifiers make raw wire JSON roughly three times longer than positional constructor calls.
2. Embedding full JSON Schemas in the system prompt consumes roughly twice as many input tokens as compact component signatures.
3. Deeply nested braces, quotes, and manual component IDs increase syntax and reference errors, especially on smaller, faster models.
Inference formats separate what is best for the model to generate from what is best for the renderer to consume. The [Agent SDKs](/concepts/sdks/#agent-sdk) include two built-in formats and allow custom implementations:
| Format | Sentinel tags | SDK support | How it represents UI |
| :---------- | :--------------------------- | :--------------------------------- | :---------------------------------------------------------------------------------------------------------------------- |
| Express | `...` | TypeScript, Python, Dart (default) | Compact positional DSL with nested component calls and `$/path` data assignments, compiled to flat wire JSON. |
| Direct JSON | `...` | TypeScript, Python, Dart | Standard A2UI wire JSON emitted directly by the model, with Markdown fence stripping and incremental string healing. |
| Custom | Format-defined | TypeScript, Python, Dart | Any domain-specific grammar or model-tailored notation implemented against the SDK's `InferenceFormatFactory` contract. |
Not every A2UI agent uses free-form model generation. Regulated or deterministic workflows can also construct wire messages directly in server code (driven by a classifier, state machine, or [macro](/guides/macros/)) and send the resulting JSON to the renderer without an LLM compilation step.
## Express vs. wire JSON
In Express, the model calls component constructors with positional or keyword arguments, writes [data model](/concepts/data-binding/) values with `$/path = value`, and nests child components inline instead of inventing string IDs by hand.
The example below uses `Card`, `Column`, `Text`, and `Button` from the Basic Catalog; Express derives signatures dynamically for any custom `catalog.json` you register (whitespace and indentation are optional):
```text title="Express block (indented for readability)"
$/flight/number = "NH 105"
$/flight/gate = "B12"
root = Card(
Column([
Text($/flight/number),
Text($/flight/gate, "caption"),
Button(Text("Board now"), action=Event("check_in", {flight: $/flight/number}))
])
)
```
When the compiler reads that `` block, it flattens the nested tree and assigns deterministic IDs (`root`, `root_child`, `root_child_children_0`, and so on). In v1.0, initial data assignments fold into `createSurface.dataModel`; in v0.9.1, the compiler emits `createSurface`, `updateComponents`, and `updateDataModel`:
#### v1.0
```json
[
{
"version": "1.0",
"createSurface": {
"surfaceId": "main",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json",
"components": [
{
"id": "root",
"component": "Card",
"child": "root_child"
},
{
"id": "root_child",
"component": "Column",
"children": ["root_child_children_0", "root_child_children_1", "root_child_children_2"]
},
{
"id": "root_child_children_0",
"component": "Text",
"text": {"@path": "/flight/number"}
},
{
"id": "root_child_children_1",
"component": "Text",
"text": {"@path": "/flight/gate"},
"variant": "caption"
},
{
"id": "root_child_children_2",
"component": "Button",
"child": "root_child_children_2_child",
"action": {
"event": {
"name": "check_in",
"context": {
"flight": {"@path": "/flight/number"}
}
}
}
},
{
"id": "root_child_children_2_child",
"component": "Text",
"text": "Board now"
}
],
"dataModel": {
"flight": {
"number": "NH 105",
"gate": "B12"
}
}
}
}
]
```
#### v0.9.1
```json
[
{
"version": "v0.9",
"createSurface": {
"surfaceId": "main",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
},
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [
{
"id": "root",
"component": "Card",
"child": "root_child"
},
{
"id": "root_child",
"component": "Column",
"children": ["root_child_children_0", "root_child_children_1", "root_child_children_2"]
},
{
"id": "root_child_children_0",
"component": "Text",
"text": {"path": "/flight/number"}
},
{
"id": "root_child_children_1",
"component": "Text",
"text": {"path": "/flight/gate"},
"variant": "caption"
},
{
"id": "root_child_children_2",
"component": "Button",
"child": "root_child_children_2_child",
"action": {
"event": {
"name": "check_in",
"context": {
"flight": {"path": "/flight/number"}
}
}
}
},
{
"id": "root_child_children_2_child",
"component": "Text",
"text": "Board now"
}
]
}
},
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "main",
"path": "/",
"value": {
"flight": {
"number": "NH 105",
"gate": "B12"
}
}
}
}
]
```
For the full grammar of Express, see the [A2UI Express specification proposal](https://github.com/a2ui-project/a2ui/blob/main/specification/proposals/express/a2ui_express.md). For the wire message schemas that all formats target, see [`createSurface`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/CreateSurfaceMessage), [`updateComponents`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateComponentsMessage), and [`updateDataModel`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateDataModelMessage) in the Schema Explorer.
## How inference formats stay swappable
Swapping an inference format requires no changes to catalogs, few-shot examples, or client renderers because every format in the [Agent SDK](/concepts/sdks/#agent-sdk) shares three design properties:
1. Catalog-agnostic prompt synthesis: Rather than hardcoding component names, a format's `PromptGenerator` inspects the negotiated `Catalog` schemas at runtime to derive component signatures, function signatures, and dynamic binding rules.
2. Bidirectional translation (`compile` and `decompile`): Alongside compiling model output into wire messages, each format's `Parser` can decompile wire messages back into the format's text notation. Structured few-shot `examples`, fenced `json` message blocks inside a catalog's `instructions`, and prior conversation turns can therefore be authored once as wire messages and rendered automatically in whichever format is active.
3. Incremental stream compilation and validation: Both one-shot parsing (`parseResponse` / `parse_response`) and chunked streaming (`openStream` / `open_stream`) separate conversational text from UI sentinel blocks, heal in-progress string literals on configured `progressiveKeys` (such as `"text"`), emit incremental wire deltas as statements arrive, and validate every message against `RendererState` so malformed output can be returned to the model for self-correction.
In the Agent SDKs (available for TypeScript, Python, and Dart; the Swift and Kotlin packages focus on client-side rendering), `A2uiGenerator` defaults to `ExpressFormatFactory` and accepts any `InferenceFormatFactory` globally or per request while keeping prompt generation and response parsing identical:
#### TypeScript
```typescript
import {
A2uiGenerator,
CatalogConfig,
DirectJsonFormatFactory,
ExpressFormatFactory,
} from '@a2ui/agent';
// Uses ExpressFormatFactory by default; pass any InferenceFormatFactory to change it.
const generator = new A2uiGenerator(
[new CatalogConfig(catalog)],
undefined,
new ExpressFormatFactory(),
);
// Prompt synthesis and parsing stay the same regardless of format,
// and an individual request can override the format factory if needed.
const processor = generator.createProcessor(rendererCapabilities, new DirectJsonFormatFactory());
const systemPrompt = processor.promptSnippet;
const parts = processor.parseResponse(llmOutput);
```
#### Python
```python
from a2ui.inference_formats import DirectJsonFormatFactory, ExpressFormatFactory
from a2ui.processor import A2uiGenerator, CatalogConfig
# Uses ExpressFormatFactory by default; pass any InferenceFormatFactory to change it.
generator = A2uiGenerator(
catalogs=[CatalogConfig(catalog=catalog)],
inference_format_factory=ExpressFormatFactory(),
)
# Prompt synthesis and parsing stay the same regardless of format,
# and an individual request can override the format factory if needed.
processor = generator.create_processor(
renderer_capabilities,
inference_format_factory=DirectJsonFormatFactory(),
)
system_prompt = processor.prompt_snippet
parts = processor.parse_response(llm_output)
```
#### Dart
```dart
import 'package:a2ui_agent/a2ui_agent.dart';
void illustrateFormats(
Catalog catalog,
A2uiRendererCapabilities rendererCapabilities,
String llmOutput,
) {
// Uses ExpressFormatFactory by default; pass any InferenceFormatFactory to change it.
final generator = A2uiGenerator(
catalogs: [CatalogConfig(catalog)],
inferenceFormatFactory: const ExpressFormatFactory(),
);
// Prompt synthesis and parsing stay the same regardless of format,
// and an individual request can override the format factory if needed.
final processor = generator.createProcessor(
rendererCapabilities,
inferenceFormatFactory: const DirectJsonFormatFactory(),
);
final systemPrompt = processor.promptSnippet;
final parts = processor.parseResponse(llmOutput);
}
```
Express covers v0.9, v0.9.1, and v1.0 generation with lower token cost, while Direct JSON remains useful when serving legacy v0.8 payloads in Python, debugging raw wire payloads, or calling a model fine-tuned directly on A2UI JSON schemas. Teams experimenting with alternative grammars (such as HTML-inspired or S-expression notations; see [`python/a2ui_agent/src/a2ui/inference_formats/experimental/`](https://github.com/a2ui-project/a2ui/tree/main/python/a2ui_agent/src/a2ui/inference_formats/experimental)) implement the same `InferenceFormatFactory`, `PromptGenerator`, and `Parser` interfaces.
## Token and latency benchmarks
The A2UI evaluation suite (`eval/`) benchmarks each format on the 54-sample `core_v1_0` suite using `gemini-3.8-flash` (`temperature=0.0`). For earlier benchmark background, see [Fast, reliable A2UI, with swappable inference formats](https://developers.googleblog.com/fast-reliable-a2ui/). Whether thinking is disabled (`thinking_budget=0`) or left unbounded (default), Express reduces both prompt tokens and generated code tokens while matching Direct JSON on accuracy:
| Format and thinking mode | Median input tokens | Median code tokens | Median thinking tokens | Median total output (`code + thinking`) | Pass@1 / Pass@2 | Judge quality |
| :--------------------------------- | :------------------ | :----------------- | :--------------------- | :-------------------------------------- | :-------------------- | :------------ |
| Direct JSON (`thinking_budget=0`) | 10,980 | 807 | 0 | 811 | 98.1% / 98.1% (53/54) | 97.2% |
| Express (`thinking_budget=0`) | 6,000 (-45.4%) | 297 (-63.2%, 2.7x) | 0 | 299 (-63.1%) | 98.1% / 98.1% (53/54) | 97.2% |
| Direct JSON (`unbounded` thinking) | 10,980 | 794 | 1,413 | 2,320 | 98.1% / 98.1% (53/54) | 98.1% |
| Express (`unbounded` thinking) | 6,000 (-45.4%) | 260 (-67.3%, 3.1x) | 1,447 | 1,789 (-22.9%) | 98.1% / 98.1% (53/54) | 98.1% |
---
# Messages
> Asynchronous bidirectional JSON message types and transport metadata in A2UI.
Canonical URL: https://a2ui.org/concepts/messages/
A2UI agents and client renderers communicate by exchanging asynchronous JSON messages in both directions to manage UI surfaces and handle user interactions. These message types can be sent over a variety of transports, including [Agent-to-Agent (A2A)](https://a2a-protocol.org) and [Model Context Protocol (MCP)](https://modelcontextprotocol.io).
Alongside explicit A2UI messages, implementations can use [metadata](#metadata) to include renderer context in requests sent for other purposes. For example, when a user sends a text prompt, the renderer can attach its supported catalogs or synchronized surface state to the outgoing request.
---
## Agent-to-renderer messages
Agent-to-renderer messages construct surfaces, update component hierarchies and reactive data models, clean up surfaces, invoke client-side functions, and return results for agent-side function calls. Each wire message in either direction is a JSON object with a `"version"` field (`"1.0"` in v1.0, or `"v0.9"` / `"v0.9.1"` in v0.9.1) and a single message key.
### createSurface
The `createSurface` message allocates a new UI surface identified by `surfaceId`. The `surfaceId` must be globally unique within the rendering context (the `MessageProcessor` instance on the renderer), which is typically a conversation. Creating a surface implicitly instantiates the reserved [`Surface`](/specification/v1.0/schemas/#common_types.json/$defs/Surface) container component with `child: "root"`. In v1.0, `createSurface` can also include optional surface-level custom extensions under `metadata.extensions` ([`Extensions`](/specification/v1.0/schemas/#common_types.json/$defs/Extensions)).
#### Properties
| Property | Type | Required | Description |
| :-------------- | :------ | :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `surfaceId` | string | Yes | Unique identifier for the surface. |
| `catalogId` | string | Optional (v1.0), Yes (v0.9.1) | Default catalog identifier for this surface (for example, `travel:flights`). In v1.0, individual components (`@catalogId`) and function calls (`catalogId`) can specify their own catalog when omitted here. |
| `sendDataModel` | boolean | Optional | When `true`, the renderer attaches the full surface data model in transport metadata (`a2uiRendererDataModel` in v1.0 / `a2uiClientDataModel` in v0.9.1) on every message to the creating agent. |
| `components` | array | Optional (v1.0) | Initial flat list of component definitions, allowing single-message surface creation. |
| `dataModel` | object | Optional (v1.0) | Initial root data model JSON object for the surface. |
| `metadata` | object | Optional (v1.0) | Surface-level extensions container with optional `extensions` ([`Extensions`](/specification/v1.0/schemas/#common_types.json/$defs/Extensions)). |
> **Note**
In v1.0, `catalogId` is optional, `components` and `dataModel` can be passed inline to initialize
a surface in a single message, and the v0.9.1 `theme` property is removed in favor of host
application styling.
#### Example
Creating a `"flight_search"` surface:
#### v1.0
```json
{
"version": "1.0",
"createSurface": {
"surfaceId": "flight_search",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json",
"sendDataModel": false,
"components": [
{
"id": "root",
"component": "Column",
"children": ["title", "flight_card"]
},
{
"id": "title",
"component": "Text",
"text": "Available Flights"
},
{
"id": "flight_card",
"component": "Card",
"child": "flight_info"
},
{
"id": "flight_info",
"component": "Text",
"text": {"@path": "/flights/0/summary"}
}
],
"dataModel": {
"flights": [
{"summary": "LH458 · BER -> SFO · $680", "price": 680}
]
}
}
}
```
#### v0.9.1
```json
{
"version": "v0.9",
"createSurface": {
"surfaceId": "flight_search",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"sendDataModel": false
}
}
```
---
### updateComponents
The `updateComponents` message adds or replaces component definitions in an existing surface. Relationships between components are expressed by ID references in a flat adjacency list rather than nested JSON trees, and one component in the surface must have `id: "root"`. In v1.0, individual component definitions in `components` can also carry optional component-level extensions via `@metadata.extensions` ([`ComponentCommon`](/specification/v1.0/schemas/#common_types.json/$defs/ComponentCommon)).
#### Properties
| Property | Type | Required | Description |
| :----------- | :----- | :------- | :---------------------------------------------------------------------------------------------------------------------------- |
| `surfaceId` | string | Yes | Identifier of the surface to update. |
| `components` | array | Yes | Non-empty flat array of component objects. Components whose `id` already exists in the surface replace the stored definition. |
#### Example
Populating a surface with a `Column`, header `Text`, and interactive `Button`:
#### v1.0
```json
{
"version": "1.0",
"updateComponents": {
"surfaceId": "flight_search",
"components": [
{
"id": "root",
"component": "Column",
"children": ["header", "btn_select_lh458"]
},
{
"id": "header",
"component": "Text",
"text": "Outbound Flights"
},
{
"id": "btn_select_label",
"component": "Text",
"text": "Select LH458 ($680)"
},
{
"id": "btn_select_lh458",
"component": "Button",
"child": "btn_select_label",
"variant": "primary",
"action": {
"event": {
"name": "select_flight",
"userMessage": "Selected flight LH458",
"context": {
"flightId": "LH458",
"price": {"@path": "/flights/0/price"}
}
}
}
}
]
}
}
```
#### v0.9.1
```json
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "flight_search",
"components": [
{
"id": "root",
"component": "Column",
"children": ["header", "btn_select_lh458"]
},
{
"id": "header",
"component": "Text",
"text": "Outbound Flights"
},
{
"id": "btn_select_label",
"component": "Text",
"text": "Select LH458 ($680)"
},
{
"id": "btn_select_lh458",
"component": "Button",
"child": "btn_select_label",
"variant": "primary",
"action": {
"event": {
"name": "select_flight",
"context": {
"flightId": "LH458",
"price": {"path": "/flights/0/price"}
}
}
}
}
]
}
}
```
---
### updateDataModel
The `updateDataModel` message updates the surface data model at a targeted location using an [RFC 6901 JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901), allowing the agent to refresh bound values without resending component definitions.
#### Properties
| Property | Type | Required | Description |
| :---------- | :----- | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `surfaceId` | string | Yes | Identifier of the target surface. |
| `path` | string | Optional | RFC 6901 JSON Pointer indicating where to apply the update (for example, `"/selectedFlight"`). When omitted or set to `"/"`, the update replaces the entire data model. |
| `value` | any | Yes (v1.0), Optional (v0.9.1) | Value to store at `path`. |
> **Note**
In **v1.0**, `value` is required; set `"value": null` to delete the key at `path`. In **v0.9.1**,
`value` is optional, and omitting `value` removes the key at `path`.
#### Examples
#### v1.0
Updating `/selectedFlight`:
```json
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "flight_search",
"path": "/selectedFlight",
"value": {
"flightId": "LH458",
"status": "confirmed",
"passengers": 2
}
}
}
```
Deleting `/selectedFlight` in v1.0 by setting `"value": null`:
```json
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "flight_search",
"path": "/selectedFlight",
"value": null
}
}
```
#### v0.9.1
Updating `/selectedFlight`:
```json
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "flight_search",
"path": "/selectedFlight",
"value": {
"flightId": "LH458",
"status": "confirmed",
"passengers": 2
}
}
}
```
Deleting `/selectedFlight` in v0.9.1 by omitting `value`:
```json
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "flight_search",
"path": "/selectedFlight"
}
}
```
---
### deleteSurface
The `deleteSurface` message instructs the renderer to unmount the surface identified by `surfaceId` and release its component tree and data model state.
#### Properties
| Property | Type | Required | Description |
| :---------- | :----- | :------- | :--------------------------------- |
| `surfaceId` | string | Yes | Identifier of the surface to drop. |
#### Example
Deleting the `"flight_search"` surface:
#### v1.0
```json
{
"version": "1.0",
"deleteSurface": {
"surfaceId": "flight_search"
}
}
```
#### v0.9.1
```json
{
"version": "v0.9",
"deleteSurface": {
"surfaceId": "flight_search"
}
}
```
---
### callRendererFunction (v1.0 only)
The `callRendererFunction` message is an agent-initiated remote procedure call (RPC) that directs the renderer to execute a catalog function registered on the client (for example, reading device timezone or screen dimensions). Only catalog functions declaring `allowedCallers` as `"agentOnly"` or `"rendererOrAgent"` may be called by the agent.
> **Note**
`callRendererFunction` is introduced in protocol v1.0. In v0.9.1, `server_to_client.json` does not
define an agent-to-renderer function invocation message.
#### Properties
| Property | Type | Required | Description |
| :--------------- | :----- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `functionCallId` | string | Yes | Unique correlation identifier for this call, copied verbatim into [`rendererFunctionResponse`](#rendererfunctionresponse-v10-only). |
| `callFunction` | object | Yes | [`FunctionCall`](/specification/v1.0/schemas/#common_types.json/$defs/FunctionCall) object with required `@call`, required `catalogId`, and optional `args`. |
| `surfaceId` | string | Optional | Surface ID whose data model provides the evaluation context. When omitted, the function executes in an isolated root context with `{}`. |
#### Example
Calling a client-side `getDeviceTimezone` function on the renderer:
```json
{
"version": "1.0",
"callRendererFunction": {
"surfaceId": "flight_search",
"functionCallId": "call_tz_101",
"callFunction": {
"@call": "getDeviceTimezone",
"catalogId": "device:sensors",
"args": {}
}
}
}
```
---
### agentFunctionResponse (v1.0 only)
The `agentFunctionResponse` message returns the execution result or structured error for a preceding [`callAgentFunction`](#callagentfunction-v10-only) message sent by the renderer.
#### Properties
| Property | Type | Required | Description |
| :--------------- | :----- | :---------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| `functionCallId` | string | Yes | Correlation identifier matching the initiating `callAgentFunction` message. |
| `value` | any | Conditional | Return value produced on success. Exactly one of `value` or `error` must be present. |
| `error` | object | Conditional | Error object with required `code` (`string`) and `message` (`string`) fields if execution failed. Mutually exclusive with `value`. |
#### Example
Returning baggage allowance data for an agent function call:
```json
{
"version": "1.0",
"agentFunctionResponse": {
"functionCallId": "call_baggage_202",
"value": {
"allowedBags": 2,
"maxWeightKg": 23,
"feePerExtraBag": 75
}
}
}
```
---
## Renderer-to-agent messages
Renderer-to-agent messages report user interactions, invoke remote functions on the agent, and return results from agent-initiated client function calls.
### action
The `action` message notifies the agent that the user triggered an event action on an interactive component (such as clicking a `Button` configured with `action.event`). Before dispatching the message, the renderer resolves any data bindings inside `action.event.context` and `action.event.userMessage` against the local data model. In v1.0, `action` can also include optional renderer-side custom extensions under `metadata.extensions` ([`Extensions`](/specification/v1.0/schemas/#common_types.json/$defs/Extensions)).
#### Properties
| Property | Type | Required | Description |
| :------------------ | :----- | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | string | Yes | Event name declared on the component's `action.event.name`. |
| `surfaceId` | string | Yes | Surface ID where the interaction originated. |
| `sourceComponentId` | string | Yes | Component `id` that triggered the action. |
| `timestamp` | string | Yes | ISO 8601 date-time string indicating when the event occurred. |
| `context` | object | Yes | Resolved key-value dictionary from `action.event.context` with all data bindings evaluated to current values. |
| `userMessage` | string | Optional (v1.0) | Human-readable string resolved from `action.event.userMessage`, suitable for adding to LLM conversation history. |
| `metadata` | object | Optional (v1.0) | Action-level extensions container with optional `extensions` ([`Extensions`](/specification/v1.0/schemas/#common_types.json/$defs/Extensions)). |
#### Example
Reporting a `"select_flight"` button action with resolved context:
#### v1.0
```json
{
"version": "1.0",
"action": {
"name": "select_flight",
"surfaceId": "flight_search",
"sourceComponentId": "btn_select_lh458",
"timestamp": "2026-11-02T10:15:30.000Z",
"userMessage": "Selected flight LH458",
"context": {
"flightId": "LH458",
"price": 680
}
}
}
```
#### v0.9.1
```json
{
"version": "v0.9",
"action": {
"name": "select_flight",
"surfaceId": "flight_search",
"sourceComponentId": "btn_select_lh458",
"timestamp": "2026-11-02T10:15:30.000Z",
"context": {
"flightId": "LH458",
"price": 680
}
}
}
```
---
### callAgentFunction (v1.0 only)
The `callAgentFunction` message is a renderer-initiated RPC requesting that the agent execute a function remotely (for example, checking live baggage rules or verifying a backend identifier).
#### Properties
| Property | Type | Required | Description |
| :--------------- | :----- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `surfaceId` | string | Yes | Surface ID where the call originated. |
| `functionCallId` | string | Yes | Unique correlation identifier for this call, copied verbatim into [`agentFunctionResponse`](#agentfunctionresponse-v10-only). |
| `callFunction` | object | Yes | [`FunctionCall`](/specification/v1.0/schemas/#common_types.json/$defs/FunctionCall) object with required `@call`, optional `catalogId`, and optional `args`. |
#### Example
Invoking `getBaggageAllowance` on the agent from the renderer:
```json
{
"version": "1.0",
"callAgentFunction": {
"surfaceId": "flight_search",
"functionCallId": "call_baggage_202",
"callFunction": {
"@call": "getBaggageAllowance",
"catalogId": "travel:flights",
"args": {
"flightId": "LH458",
"cabin": "economy"
}
}
}
}
```
---
### rendererFunctionResponse (v1.0 only)
The `rendererFunctionResponse` message delivers the renderer's return value or structured execution error for a preceding [`callRendererFunction`](#callrendererfunction-v10-only) message.
#### Properties
| Property | Type | Required | Description |
| :--------------- | :----- | :---------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| `functionCallId` | string | Yes | Correlation identifier matching the initiating `callRendererFunction` message. |
| `value` | any | Conditional | Return value produced on success. Exactly one of `value` or `error` must be present. |
| `error` | object | Conditional | Error object with required `code` (`string`) and `message` (`string`) fields if execution failed. Mutually exclusive with `value`. |
#### Example
Returning the device timezone for a preceding `callRendererFunction` call:
```json
{
"version": "1.0",
"rendererFunctionResponse": {
"functionCallId": "call_tz_101",
"value": {
"timeZone": "Europe/Berlin",
"utcOffsetMinutes": 60
}
}
}
```
---
### Metadata
Transport metadata lets the renderer attach A2UI context to outgoing requests sent for other purposes (such as user text prompts in A2A or request metadata in MCP), flowing from the renderer to the agent.
| Metadata key (v1.0) | Metadata key (v0.9.1) | Purpose |
| :------------------------- | :----------------------- | :-------------------------------------------------------------------------------------------- |
| `a2uiRendererCapabilities` | `a2uiClientCapabilities` | Advertises supported protocol versions, `supportedCatalogIds`, and optional `inlineCatalogs`. |
| `a2uiRendererDataModel` | `a2uiClientDataModel` | Sends full local data models for active surfaces created with `sendDataModel: true`. |
Renderer capabilities (`a2uiRendererCapabilities` in v1.0 / `a2uiClientCapabilities` in v0.9.1) are an optional mechanism that loosely coupled systems can use to negotiate which protocol versions and catalogs can be used when they are not known in advance. Systems where the agent and client are owned by the same party may not need capability negotiation if the protocol version and catalogs are agreed in advance. Agents can also advertise their static catalog support ahead of time via [`agent_capabilities.json`](/specification/v1.0/schemas/#agent_capabilities.json) (v1.0) or [`server_capabilities.json`](/specification/v0.9.1/schemas/#server_capabilities.json) (v0.9.1), such as in an A2A Agent Card.
When any active surface was created with `sendDataModel: true`, the renderer attaches `a2uiRendererDataModel` (or `a2uiClientDataModel` in v0.9.1) on outgoing messages to that agent so local input state stays synchronized without embedding full data models inside `action` payloads.
#### Example
Attaching capabilities and synchronized surface state to an outgoing transport request:
#### v1.0
```json
{
"metadata": {
"a2uiRendererCapabilities": {
"1.0": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json",
"travel:flights"
]
}
},
"a2uiRendererDataModel": {
"version": "1.0",
"surfaces": {
"flight_search": {
"selectedFlight": "LH458",
"passengers": 2
}
}
}
}
}
```
#### v0.9.1
```json
{
"metadata": {
"a2uiClientCapabilities": {
"v0.9": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"travel:flights"
]
}
},
"a2uiClientDataModel": {
"version": "v0.9",
"surfaces": {
"flight_search": {
"selectedFlight": "LH458",
"passengers": 2
}
}
}
}
}
```
---
# SDKs
> Official A2UI Core, Agent, and Renderer SDKs across TypeScript, Python, Dart, Swift, and Kotlin.
Canonical URL: https://a2ui.org/concepts/sdks/
Official A2UI SDKs are organized into three layers:
- **Core SDK** for shared protocol models and surface state.
- **Agent SDK** for catalog-driven prompt generation and model output compilation.
- **Renderer SDK** for mounting surface state in a client UI framework.
Both the Agent SDK and the Renderer SDK build on their language's Core SDK, so server-side validation and client-side message resolution share one implementation per language. Client-server applications typically pair two layers on each side of a network boundary, such as a Python agent server communicating with a SwiftUI or Jetpack Compose client. Codebases that handle both model inference and UI rendering in the same language, such as TypeScript web apps or Dart and Flutter apps, can use all three layers together in one project.
## Core SDK
Every Agent SDK and Renderer SDK depends on its language's Core SDK, which implements the protocol data structures, reactive state containers, and expression evaluators common to agents and clients.
### Primary functions
1. Parses and serializes A2UI [wire messages](/concepts/messages/) across supported protocol versions (v1.0, v0.9.1, and v0.8 in TypeScript and Python).
2. Loads [Catalog](/concepts/catalogs/) JSON Schemas and validates component hierarchies, placement constraints, and message payloads.
3. Maintains per-surface component graphs and JSON [data model](/concepts/data-binding/) state while applying incremental updates by component ID and JSON Pointer path.
4. Evaluates data bindings, collection templates, string interpolation expressions, function invocations, and validation checks.
### Packages
| Language | Published packages | Versions | Source |
| :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------- | :-------------------------------------------------------------------------------------------------- |
| TypeScript | [@a2ui/web_core](https://www.npmjs.com/package/@a2ui/web_core) | v0.8, v0.9.1, v1.0 | [typescript/core/web_core](https://github.com/a2ui-project/a2ui/tree/main/typescript/core/web_core) |
| Python | [a2ui-core](https://pypi.org/project/a2ui-core/) | v0.8, v0.9.1, v1.0 | [python/a2ui_core](https://github.com/a2ui-project/a2ui/tree/main/python/a2ui_core) |
| Dart | [a2ui_core](https://pub.dev/packages/a2ui_core) | v0.9.1, v1.0 | [dart/a2ui_core](https://github.com/a2ui-project/a2ui/tree/main/dart/a2ui_core) |
| Swift | [A2UISwiftCore](https://github.com/a2ui-project/a2ui/blob/main/Package.swift)
[BasicCatalog](https://github.com/a2ui-project/a2ui/blob/main/Package.swift) | v0.9.1, v1.0 | [swift/core](https://github.com/a2ui-project/a2ui/tree/main/swift/core) |
| Kotlin | [androidx.a2ui](https://developer.android.com/jetpack/androidx/releases/a2ui) | v0.9.1 | [AndroidX a2ui](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:a2ui/) |
All v0.9.1 packages accept v0.9 wire envelopes and catalog IDs. Swift packages are distributed through Swift Package Manager from [`a2ui-project/a2ui`](https://github.com/a2ui-project/a2ui) (the `A2UISwiftCore` product exposes the `A2UICore` module), and the AndroidX group on Google Maven publishes the `a2ui-model` and `a2ui-engine` artifacts.
Most applications install a Core SDK automatically through an [Agent SDK](#agent-sdk) or [Renderer SDK](#renderer-sdk), though you can depend on a Core SDK directly to build headless state processors or custom UI adapters. To implement a Core SDK in another language, read [`blueprints/modules`](https://github.com/a2ui-project/a2ui/tree/main/blueprints/modules) and validate against the YAML suites in [`conformance/core`](https://github.com/a2ui-project/a2ui/tree/main/conformance/core).
## Agent SDK
An Agent SDK connects a language model or agent runtime to the A2UI protocol while leaving model selection, tool execution, and conversation state under your application's control.
### Primary functions
1. Negotiates active [Catalog](/concepts/catalogs/) definitions and protocol versions (v0.9.1 and v1.0) against the capabilities reported by the client renderer (see [Overview](/concepts/overview/)).
2. Generates skills, system prompt instructions, and examples directly from catalog schemas and prompt instructions, replacing hand-written prompts that become outdated when components change.
3. Compiles streaming or batch model output from [inference formats](/concepts/inference-formats/) (such as compact Express syntax, which cuts output tokens without client changes, or chunk-healed Direct JSON) into standard A2UI [wire messages](/concepts/messages/).
4. Validates outgoing messages statefully against active surface state on the server so broken component IDs, placement violations, or schema mismatches can be fed back to the model for self-correction before transmission.
5. Supports optional server-side [`@macro`](/guides/macros/) expansion in Python and integrates with agent frameworks and transports including [Google ADK](https://github.com/google/adk-python) and the [A2A protocol](https://a2a-protocol.org/).
### Packages
| Language | Published packages | Versions | Source |
| :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TypeScript | [@a2ui/agent](https://www.npmjs.com/package/@a2ui/agent) | v0.9.1, v1.0 | [typescript/agents/a2ui_agent](https://github.com/a2ui-project/a2ui/tree/main/typescript/agents/a2ui_agent) |
| Python | [a2ui-agent-sdk](https://pypi.org/project/a2ui-agent-sdk/)
[a2ui-adk](https://pypi.org/project/a2ui-adk/) (ADK)
[a2ui-a2a](https://pypi.org/project/a2ui-a2a/) (A2A) | v0.8, v0.9.1, v1.0 | [python/a2ui_agent](https://github.com/a2ui-project/a2ui/tree/main/python/a2ui_agent)
[python/a2ui_adk](https://github.com/a2ui-project/a2ui/tree/main/python/a2ui_adk)
[python/a2ui_a2a](https://github.com/a2ui-project/a2ui/tree/main/python/a2ui_a2a) |
| Dart | [a2ui_agent](https://pub.dev/packages/a2ui_agent) | v0.9.1, v1.0 | [dart/a2ui_agent](https://github.com/a2ui-project/a2ui/tree/main/dart/a2ui_agent) |
Swift and Kotlin target client-side rendering; for legacy v0.8 and v0.9 JVM servers, [`kotlin/agent_sdk_legacy`](https://github.com/a2ui-project/a2ui/tree/main/kotlin/agent_sdk_legacy) remains in the repository.
### Go and community SDKs
For **Go** services and CLI clients, community-maintained libraries cover server-side validation, streaming parsers, and terminal rendering—including [`satorunooshie/a2ui-go`](https://github.com/satorunooshie/a2ui-go), [`tmc/a2ui`](https://pkg.go.dev/github.com/tmc/a2ui), [`charmbracelet/a2tea`](https://github.com/charmbracelet/a2tea), and [`muncus/a2term`](https://github.com/muncus/a2term) (listed with version details in [Community Projects](/ecosystem/renderers/)).
To implement a conformance-verified Core or Agent SDK in Go, Rust, C#, or another language, follow the language-agnostic specifications in [`blueprints/modules`](https://github.com/a2ui-project/a2ui/tree/main/blueprints/modules), verify behavioral parity against the YAML suites in [`conformance/core`](https://github.com/a2ui-project/a2ui/tree/main/conformance/core) and [`conformance/agent`](https://github.com/a2ui-project/a2ui/tree/main/conformance/agent), and open a pull request to add your package to [Community Projects](/ecosystem/renderers/#how-to-submit-a-project).
See [Using a catalog in an agent](/concepts/catalogs/#using-a-catalog-in-an-agent) and [Inference Formats](/concepts/inference-formats/#how-inference-formats-stay-swappable) for TypeScript, Python, and Dart examples of configuring `A2uiGenerator`, negotiating capabilities, and compiling model responses, or run a working agent in the [Quickstart](/quickstart/).
## Renderer SDK
A Renderer SDK connects a Core SDK's resolved surface state to a client UI framework. Official Renderer SDKs include a built-in implementation of the [Basic Catalog](/concepts/catalogs/#the-basic-catalog) styled through the host framework's theming system, and let you register custom catalogs with per-component rendering logic and local functions (see [How to Define a Catalog](/guides/defining-a-catalog/)).
### Primary functions
1. Applies incremental [surface, component, and data model updates](/concepts/messages/) across supported protocol versions (v0.9.1, v1.0, and v0.8 on Web), updating only changed subtrees so unchanged siblings keep focus and local UI state.
2. Evaluates [reactive data bindings](/concepts/data-binding/), nested collection scopes, relative JSON Pointers, and local two-way input edits.
3. Executes [local catalog functions](/concepts/catalogs/#functions) and [reactive validation checks](/specification/v1.0-a2ui/#component-validation--check-rules), enforcing v1.0 caller and user-activation rules.
4. Builds capability and data-model sync metadata payloads for your transport layer, dispatches user action events and structured error messages, and handles v1.0 bidirectional function RPCs.
5. Maps protocol accessibility attributes to host accessibility APIs across Flutter, Web WAI-ARIA, SwiftUI, and Jetpack Compose.
### Packages
| Framework | Published packages | Versions | Source |
| :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Flutter | [a2ui_flutter](https://pub.dev/packages/a2ui_flutter) | v0.9.1, v1.0 | [dart/a2ui_flutter](https://github.com/a2ui-project/a2ui/tree/main/dart/a2ui_flutter) |
| React | [@a2ui/react](https://www.npmjs.com/package/@a2ui/react) | v0.8, v0.9.1, v1.0 | [typescript/renderers/react](https://github.com/a2ui-project/a2ui/tree/main/typescript/renderers/react) |
| Lit | [@a2ui/lit](https://www.npmjs.com/package/@a2ui/lit) | v0.8, v0.9.1, v1.0 | [typescript/renderers/lit](https://github.com/a2ui-project/a2ui/tree/main/typescript/renderers/lit) |
| Angular | [@a2ui/angular](https://www.npmjs.com/package/@a2ui/angular) | v0.8, v0.9.1, v1.0 | [typescript/renderers/angular](https://github.com/a2ui-project/a2ui/tree/main/typescript/renderers/angular) |
| SwiftUI | [A2UISwiftUI](https://github.com/a2ui-project/a2ui/blob/main/Package.swift)
[BasicCatalogSwiftUI](https://github.com/a2ui-project/a2ui/blob/main/Package.swift) | v0.9.1, v1.0 | [swift/swiftui](https://github.com/a2ui-project/a2ui/tree/main/swift/swiftui) |
| Jetpack Compose | [androidx.a2ui.compose](https://developer.android.com/jetpack/androidx/releases/a2ui-compose)
[material3-a2ui](https://developer.android.com/jetpack/androidx/releases/compose-material3) | v0.9.1 | [AndroidX a2ui](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:a2ui/) · [material3-a2ui](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material3/material3-a2ui/) · [Docs](https://developer.android.com/develop/ui/compose/agentic) |
On Web, [`@a2ui/web_core`](https://www.npmjs.com/package/@a2ui/web_core) provides the shared Basic Catalog implementation used by React, Lit, and Angular, [`@a2ui/markdown-it`](https://www.npmjs.com/package/@a2ui/markdown-it) ([source](https://github.com/a2ui-project/a2ui/tree/main/typescript/renderers/markdown-it)) renders Markdown inside text components, and [`@a2ui/catalog-mcp`](https://www.npmjs.com/package/@a2ui/catalog-mcp) adds MCP app components (see [Call MCP Tools from A2UI](/guides/mcp-catalog/)).
See [Registering a catalog in a renderer](/concepts/catalogs/#registering-a-catalog-in-a-renderer) and [How to Define a Catalog](/guides/defining-a-catalog/) for Flutter, Web, SwiftUI, and Jetpack Compose examples of setting up a message processor, registering component widgets and local functions, and mounting surfaces, or run a React or Flutter client in the [Quickstart](/quickstart/). Read [Authoring Universal Web Components](/guides/authoring-universal-components/) to share custom components across React, Lit, and Angular, or see the [Android Jetpack Compose guide](https://developer.android.com/develop/ui/compose/agentic) for Android integration. Browse [Community Projects](/ecosystem/renderers/) for community renderers, or consult [`blueprints/modules`](https://github.com/a2ui-project/a2ui/tree/main/blueprints/modules) and [`conformance`](https://github.com/a2ui-project/a2ui/tree/main/conformance) to build a custom framework adapter.
---
# Render A2UI Inside an MCP App
> Bundle an A2UI renderer inside an MCP App ui:// resource so existing MCP hosts can display interactive A2UI surfaces without native host code.
Canonical URL: https://a2ui.org/guides/a2ui-in-mcp-apps/
An **[MCP App](https://modelcontextprotocol.io/extensions/apps/overview)** can bundle an embedded A2UI web renderer inside its `ui://` HTML resource (`text/html;profile=mcp-app`) so existing [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) hosts display interactive A2UI surfaces without adding A2UI renderer code to the host application.
Instead of requiring every MCP client (such as Claude Desktop, the MCP Inspector, or an IDE) to install an A2UI SDK, your MCP server ships a self-contained HTML bundle that mounts `` inside the host's isolated iframe. When tools on the server return `application/a2ui+json` payloads, the embedded app feeds those messages into its own [`MessageProcessor`](/concepts/sdks/#core-sdk) and forwards user actions back through the MCP Apps `tools/call` channel.
> **Note**
See the [MCP Integration Overview](/guides/mcp-overview/) to compare all four MCP patterns. Use this
pattern when the outer host supports MCP Apps (`ui://` HTML iframes) rather than A2UI natively.
For the inverse architecture (embedding third-party MCP App widgets inside your own A2UI host),
see [Embed MCP Apps in A2UI Surfaces](/guides/mcp-apps-in-a2ui/). If both the host and server support
A2UI natively and you want to serve reusable A2UI layout templates over MCP without an iframe, see
[Serve A2UI Layouts from an MCP Server](/guides/a2ui-over-mcp/) and [Call MCP Tools from
A2UI](/guides/mcp-catalog/).
---
## Quick start: run the community sample
The [`samples/community/mcp/a2ui-in-mcpapps/`](https://github.com/a2ui-project/a2ui/blob/main/samples/community/mcp/a2ui-in-mcpapps/README.md) directory contains a runnable end-to-end sample with a Python MCP server (`server/server.py`), two single-file Angular micro-apps (`ui://editor/app` for a generative document editor and `ui://basic/app` for a counter), and a sample Angular MCP Apps host (`client/`) that runs a double-iframe sandbox on `http://localhost:4200`.
> **Note**
The community sample currently emits A2UI `v0.8` messages (`surfaceUpdate`, `dataModelUpdate`, and
`beginRendering`) rendered by `@a2ui/angular` `0.10.x` inside the micro-apps. The implementation
walkthrough below updates this pattern to `v1.0` and `v0.9.1` wire payloads with `@a2ui/lit` and
`@a2ui/web_core`.
### Prerequisites
Install these tools before building and running the sample from a clone of the A2UI repository:
1. Python 3.10 or later and [`uv`](https://docs.astral.sh/uv/) for the Python MCP server (`server/.python-version` targets Python 3.13 when managed by `uv`).
2. Node.js 22.12 or later with Corepack enabled (`corepack enable`) so `yarn` resolves the standalone Yarn workspace under `samples/community/`.
3. A `GOOGLE_API_KEY` from [Google AI Studio](https://aistudio.google.com/apikey) so `server/smart_editor_agent.py` can initialize its Gemini client at startup and generate contextual editor controls (if a Gemini call fails at runtime, the editor falls back to preset slider controls).
### Build the sandbox relay and micro-apps
The server serves single-file HTML bundles from `server/apps/public/`, which are git-ignored in a fresh checkout. Install the `samples/community` workspace dependencies, build the host's outer sandbox relay, and build both micro-apps from the repository root:
```bash
cd samples/community
yarn install
# Build the host's outer sandbox relay (client/public/sandbox_iframe/)
yarn workspace a2ui-in-mcpapps-client run build:sandbox
# Build and inline the Editor micro-app (server/apps/public/editor.html)
yarn workspace basic-mcp-app-angular-editor run build:all
# Build and inline the Basic Counter micro-app (server/apps/public/app.html)
yarn workspace basic-mcp-app-angular run build:all
```
When iterating on a single micro-app, you can run `yarn build:all` directly inside `samples/community/mcp/a2ui-in-mcpapps/server/apps/editor` or `samples/community/mcp/a2ui-in-mcpapps/server/apps/src`.
### Start the MCP server and host client
Open two terminals starting from the repository root.
1. In the first terminal, start the Python MCP server over Server-Sent Events (SSE) on `http://127.0.0.1:8000/sse` (pass `--transport stdio` instead when connecting a desktop MCP host over standard input/output):
```bash
cd samples/community/mcp/a2ui-in-mcpapps/server
export GOOGLE_API_KEY="your-api-key" # Required at startup by smart_editor_agent.py
uv sync
uv run python server.py --transport sse --port 8000
```
2. In the second terminal, start the sample Angular MCP Apps host on `http://localhost:4200`:
```bash
cd samples/community/mcp/a2ui-in-mcpapps/client
yarn start
```
### Try both views in the browser
Open `http://localhost:4200` to test how the sandboxed micro-apps render A2UI payloads returned by the MCP server:
1. Keep **Generative Editor App** selected and click **Load Selected App**. The host calls `get_editor_app` and loads `ui://editor/app` (`editor.html`). Select more than 10 characters of text inside any Editor.js paragraph; the app calls `smart_editor_get_controls`, renders generated A2UI controls (`Slider`, `CheckBox`, or `MultipleChoice`) in the right-hand panel, and calls `smart_editor_apply` when you click **Generate Revision** to rewrite the passage and swap in **Accept** and **Reject** A2UI buttons.
2. Switch the selector to **Basic Counter App** and click **Load Selected App**. The host calls `get_basic_app`, mounts `ui://basic/app` (`app.html`), and delivers the initial A2UI counter surface via `ui/notifications/tool-result`. Clicking **Increase counter** inside the rendered A2UI card calls `increase_counter` on the MCP server and updates `/counter` in place.
---
## Architecture and end-to-end flow
In a native A2UI client integration, the outer host application owns `MessageProcessor` and renders A2UI components directly in its DOM. Embedding A2UI inside an MCP App moves the A2UI runtime across the iframe boundary so any standards-compliant [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) host can render A2UI surfaces unchanged:
1. The **MCP server** publishes one or more self-contained HTML bundles under `ui://` URIs (`mimeType: "text/html;profile=mcp-app"`), alongside entry tools and UI callback tools that return `EmbeddedResource` items carrying `application/a2ui+json` messages.
2. The **MCP host application** connects to the MCP server, inspects `_meta.ui.resourceUri` on the invoked tool, fetches the `ui://` HTML via `resources/read`, and mounts an outer sandbox proxy iframe (`sandbox.html`) that injects the HTML into an inner `srcdoc` iframe with `sandbox="allow-scripts"`.
3. The **embedded MCP App** runs inside that opaque-origin `srcdoc` frame. It bundles its own A2UI web renderer (`MessageProcessor`, component catalogs, and ``), renders `application/a2ui+json` payloads alongside any custom web views (such as the Editor.js canvas in `ui://editor/app`), and translates A2UI action events into JSON-RPC 2.0 `tools/call` requests relayed through the host.
MCP Apps tool descriptors also scope which actor can invoke each tool via `_meta.ui.visibility` (as shown in [`server/server.py`](https://github.com/a2ui-project/a2ui/blob/main/samples/community/mcp/a2ui-in-mcpapps/server/server.py)):
1. Entry tools such as `get_editor_app` and `get_basic_app` declare `_meta.ui.resourceUri` and `visibility: ["model"]`, exposing a single launcher tool to the language model.
2. Fine-grained UI interaction tools such as `increase_counter`, `smart_editor_get_controls`, and `smart_editor_apply` declare `visibility: ["app"]`, allowing the sandboxed view to call them via `tools/call` without adding UI callback noise to the model's tool list.
The sequence diagram below traces the full message lifecycle across the MCP server, the outer MCP host application, the sandbox proxy iframe, and the embedded MCP App running the A2UI renderer. During startup, the host lists tools to discover `_meta.ui.resourceUri` and `_meta.ui.visibility`, invokes the entry tool via `tools/call`, fetches the `ui://` HTML document via `resources/read`, and injects it through the sandbox proxy into the isolated `srcdoc` iframe. Once the embedded app completes the `ui/initialize` and `ui/notifications/initialized` handshake, the host forwards the entry tool's result via `ui/notifications/tool-result`, and the app feeds the embedded `application/a2ui+json` messages into `MessageProcessor` to mount the A2UI surface. When the user clicks a button or submits a form on that surface, `MessageProcessor` dispatches an action, the app sends a JSON-RPC `tools/call` request through the proxy to the host, and the server returns a new `application/a2ui+json` payload (such as `updateDataModel`) that updates the rendered surface in place.
```mermaid
sequenceDiagram
participant Server as MCP Server
participant Host as MCP Host Application
participant Proxy as Sandbox Proxy Iframe
participant App as Embedded MCP App + A2UI Renderer
Host->>Server: tools/list (discover _meta.ui.resourceUri & visibility)
Server-->>Host: Tool definitions
Host->>Server: tools/call (open_counter_app)
Server-->>Host: CallToolResult (EmbeddedResource application/a2ui+json)
Host->>Server: resources/read (ui://counter/app)
Server-->>Host: TextResourceContents (HTML MCP App bundle)
Host->>Proxy: Load sandbox proxy and send ui/notifications/sandbox-resource-ready
Proxy->>App: Inject self-contained HTML into srcdoc (allow-scripts)
App->>Host: ui/initialize (relayed via Proxy)
Host-->>App: InitializeResult (hostCapabilities, hostContext)
App->>Host: ui/notifications/initialized
Host->>App: ui/notifications/tool-result (initial CallToolResult)
Note over App: Extract application/a2ui+json and run MessageProcessor.processMessages
Note over App: User clicks Button on a2ui-surface
App->>Host: tools/call (increase_counter, arguments from action.context)
Host->>Server: tools/call (increase_counter)
Server-->>Host: CallToolResult (updateDataModel in application/a2ui+json)
Host-->>App: CallToolResult response
Note over App: Apply updateDataModel to MessageProcessor
```
---
## Build an MCP App with an embedded A2UI renderer
The three steps below build a minimal MCP App using `@modelcontextprotocol/ext-apps`, `@a2ui/lit`, `@a2ui/web_core`, and Vite.
### Step 1: Register the ui:// resource and MCP tools
Start by defining the A2UI messages that your server tools return inside `EmbeddedResource` items (`mimeType: "application/a2ui+json"`). The example below uses the Basic Catalog, though A2UI catalogs are domain-specific and your embedded bundle can register any custom catalog (see [How to Define a Catalog](/guides/defining-a-catalog/)).
In `v1.0`, protocol messages set `"version": "1.0"`, expressions use `@path` and `@call`, component IDs follow UAX #31 identifier syntax (such as `main_column`), and `Text` supports Markdown headings. In `v0.9.1`, messages set `"version": "v0.9"`, expressions use `path` and `call` (with `"returnType": "string"` on string-valued function calls), and `Text.variant` includes `"h3"`.
Here is the initial surface payload returned by `open_counter_app`:
#### v1.0
```json
[
{
"version": "1.0",
"createSurface": {
"surfaceId": "counter_card",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"
}
},
{
"version": "1.0",
"updateComponents": {
"surfaceId": "counter_card",
"components": [
{
"id": "root",
"component": "Card",
"child": "main_column"
},
{
"id": "main_column",
"component": "Column",
"children": ["title_text", "count_text", "increment_button"]
},
{
"id": "title_text",
"component": "Text",
"text": "### Live counter"
},
{
"id": "count_text",
"component": "Text",
"text": {
"@call": "formatString",
"args": {
"value": "Count: ${/count}"
}
}
},
{
"id": "increment_button_text",
"component": "Text",
"text": "Increase counter"
},
{
"id": "increment_button",
"component": "Button",
"child": "increment_button_text",
"variant": "primary",
"action": {
"event": {
"name": "increase_counter",
"context": {
"step": 1
}
}
}
}
]
}
},
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "counter_card",
"path": "/",
"value": {
"count": 0
}
}
}
]
```
#### v0.9.1
```json
[
{
"version": "v0.9",
"createSurface": {
"surfaceId": "counter_card",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
},
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "counter_card",
"components": [
{
"id": "root",
"component": "Card",
"child": "main_column"
},
{
"id": "main_column",
"component": "Column",
"children": ["title_text", "count_text", "increment_button"]
},
{
"id": "title_text",
"component": "Text",
"text": "Live counter",
"variant": "h3"
},
{
"id": "count_text",
"component": "Text",
"text": {
"call": "formatString",
"args": {
"value": "Count: ${/count}"
},
"returnType": "string"
}
},
{
"id": "increment_button_text",
"component": "Text",
"text": "Increase counter"
},
{
"id": "increment_button",
"component": "Button",
"child": "increment_button_text",
"variant": "primary",
"action": {
"event": {
"name": "increase_counter",
"context": {
"step": 1
}
}
}
}
]
}
},
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "counter_card",
"path": "/",
"value": {
"count": 0
}
}
}
]
```
When the embedded app invokes `increase_counter`, the tool returns only an [`updateDataModel`](/specification/v1.0/schemas/#agent_to_renderer.json/$defs/UpdateDataModelMessage) message (`server_to_client.json` in `v0.9.1`) so the existing surface updates `/count` without rebuilding its component tree:
#### v1.0
```json
[
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "counter_card",
"path": "/count",
"value": 1
}
}
]
```
#### v0.9.1
```json
[
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "counter_card",
"path": "/count",
"value": 1
}
}
]
```
Register the `ui://counter/app` HTML resource (`text/html;profile=mcp-app`), the model-facing `open_counter_app` entry tool, and the app-only `increase_counter` callback tool on your MCP server:
#### TypeScript
```typescript
import fs from 'node:fs';
import {
RESOURCE_MIME_TYPE,
registerAppResource,
registerAppTool,
} from '@modelcontextprotocol/ext-apps/server';
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {z} from 'zod';
const A2UI_MIME_TYPE = 'application/a2ui+json';
const APP_URI = 'ui://counter/app';
let count = 0;
export function createCounterMcpServer(bundlePath: string): McpServer {
const server = new McpServer({name: 'a2ui-counter-mcp-app', version: '1.0.0'});
registerAppResource(
server,
'Counter MCP App',
APP_URI,
{
description: 'Self-contained MCP App with an embedded A2UI renderer.',
mimeType: RESOURCE_MIME_TYPE,
},
async () => ({
contents: [
{
uri: APP_URI,
mimeType: RESOURCE_MIME_TYPE,
text: fs.readFileSync(bundlePath, 'utf8'),
},
],
}),
);
registerAppTool(
server,
'open_counter_app',
{
title: 'Open Counter App',
description: 'Opens the counter view and returns its initial A2UI surface.',
inputSchema: {},
_meta: {
ui: {
resourceUri: APP_URI,
visibility: ['model'],
},
},
},
async () => ({
content: [
{type: 'text', text: `Counter opened at ${count}.`},
{
type: 'resource',
resource: {
uri: 'a2ui://counter-card',
mimeType: A2UI_MIME_TYPE,
text: JSON.stringify(buildInitialSurfacePayload(count)),
},
},
],
}),
);
server.registerTool(
'increase_counter',
{
title: 'Increase Counter',
description: 'Increments the counter and returns an A2UI updateDataModel payload.',
inputSchema: {
step: z.number().int().default(1),
},
_meta: {
ui: {
visibility: ['app'],
},
},
},
async ({step}) => {
count += step;
const update = [
{
version: '1.0',
updateDataModel: {
surfaceId: 'counter_card',
path: '/count',
value: count,
},
},
];
return {
content: [
{type: 'text', text: `Count is now ${count}.`},
{
type: 'resource',
resource: {
uri: 'a2ui://counter-card/data',
mimeType: A2UI_MIME_TYPE,
text: JSON.stringify(update),
},
},
],
};
},
);
return server;
}
```
#### Python
```python
import json
from pathlib import Path
from typing import Any
from mcp import types
from mcp.server.lowlevel import Server
from mcp.server.lowlevel.helper_types import ReadResourceContents
A2UI_MIME_TYPE = "application/a2ui+json"
MCP_APP_MIME_TYPE = "text/html;profile=mcp-app"
APP_URI = "ui://counter/app"
BUNDLE_PATH = Path(__file__).parent / "dist" / "index.html"
app = Server("a2ui-counter-mcp-app")
count = 0
# Requires mcp<2 (pip install "mcp>=1.20,<2") for the 1.x low-level Server decorators.
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri=APP_URI,
name="Counter MCP App",
mimeType=MCP_APP_MIME_TYPE,
description="Self-contained MCP App with an embedded A2UI renderer.",
)
]
@app.read_resource()
async def read_resource(uri: str) -> list[ReadResourceContents]:
if str(uri) != APP_URI:
raise ValueError(f"Unknown resource: {uri}")
return [
ReadResourceContents(
content=BUNDLE_PATH.read_text(encoding="utf-8"),
mime_type=MCP_APP_MIME_TYPE,
)
]
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="open_counter_app",
title="Open Counter App",
description="Opens the counter view and returns its initial A2UI surface.",
inputSchema={"type": "object", "properties": {}},
_meta={"ui": {"resourceUri": APP_URI, "visibility": ["model"]}},
),
types.Tool(
name="increase_counter",
title="Increase Counter",
description="Increments the counter and returns an A2UI updateDataModel payload.",
inputSchema={
"type": "object",
"properties": {"step": {"type": "integer", "default": 1}},
},
_meta={"ui": {"visibility": ["app"]}},
),
]
@app.call_tool()
async def handle_call_tool(
name: str, arguments: dict[str, Any]
) -> types.CallToolResult:
global count
if name == "open_counter_app":
payload = build_initial_surface_payload(count)
return types.CallToolResult(
content=[
types.TextContent(type="text", text=f"Counter opened at {count}."),
types.EmbeddedResource(
type="resource",
resource=types.TextResourceContents(
uri="a2ui://counter-card",
mimeType=A2UI_MIME_TYPE,
text=json.dumps(payload),
),
),
]
)
if name == "increase_counter":
count += int(arguments.get("step", 1))
update = [
{
"version": "1.0",
"updateDataModel": {
"surfaceId": "counter_card",
"path": "/count",
"value": count,
},
}
]
return types.CallToolResult(
content=[
types.TextContent(type="text", text=f"Count is now {count}."),
types.EmbeddedResource(
type="resource",
resource=types.TextResourceContents(
uri="a2ui://counter-card/data",
mimeType=A2UI_MIME_TYPE,
text=json.dumps(update),
),
),
]
)
raise ValueError(f"Unknown tool: {name}")
```
### Step 2: Embed the A2UI renderer and forward actions in the MCP App
Inside the MCP App project, install `@a2ui/lit`, `@a2ui/markdown-it`, `@a2ui/web_core`, `lit`, and `@modelcontextprotocol/ext-apps`:
```bash
npm install @a2ui/lit @a2ui/markdown-it @a2ui/web_core lit @modelcontextprotocol/ext-apps
```
Create an `index.html` entry point with a mount element for your surface:
```html
Counter MCP App
```
In `src/main.ts`, register `renderMarkdown` via `setMarkdownRenderer`, initialize `MessageProcessor` with `basicCatalog` (or your custom catalog), mount `` when a surface is created, and use `App` from `@modelcontextprotocol/ext-apps` to handle the `ui/initialize` handshake, incoming `ui/notifications/tool-result` payloads, automatic iframe resizing (`autoResize: true`), and outbound `tools/call` requests whenever a component dispatches an [action](/concepts/actions-and-functions/):
#### v1.0
```typescript
import {html, render} from 'lit';
import '@a2ui/lit';
import {renderMarkdown} from '@a2ui/markdown-it';
import {
basicCatalog,
MessageProcessor,
setMarkdownRenderer,
type ProcessableMessagePayload,
} from '@a2ui/web_core/v1_0';
import {App} from '@modelcontextprotocol/ext-apps';
import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js';
setMarkdownRenderer(renderMarkdown);
const A2UI_MIME_TYPE = 'application/a2ui+json';
const rootEl = document.getElementById('surface-root')!;
const app = new App(
{name: 'counter-mcp-app', version: '1.0.0'},
{availableDisplayModes: ['inline']},
{autoResize: true},
);
function applyToolResult(result: CallToolResult): void {
for (const block of result.content ?? []) {
if (
block.type === 'resource' &&
'text' in block.resource &&
(block.resource.mimeType === A2UI_MIME_TYPE ||
block.resource.mimeType === 'application/json+a2ui')
) {
const messages = JSON.parse(block.resource.text) as ProcessableMessagePayload;
processor.processMessages(messages);
}
}
}
const processor = new MessageProcessor([basicCatalog], async action => {
const result = await app.callServerTool({
name: action.name,
arguments: action.context,
});
applyToolResult(result);
});
processor.onSurfaceCreated(surface => {
render(html``, rootEl);
});
app.ontoolresult = params => {
applyToolResult(params);
};
await app.connect();
```
#### v0.9.1
```typescript
import {html, render} from 'lit';
import {basicCatalog} from '@a2ui/lit/v0_9';
import {renderMarkdown} from '@a2ui/markdown-it';
import {
MessageProcessor,
setMarkdownRenderer,
type ProcessableMessagePayload,
} from '@a2ui/web_core/v0_9';
import {App} from '@modelcontextprotocol/ext-apps';
import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js';
setMarkdownRenderer(renderMarkdown);
const A2UI_MIME_TYPE = 'application/a2ui+json';
const rootEl = document.getElementById('surface-root')!;
const app = new App(
{name: 'counter-mcp-app', version: '1.0.0'},
{availableDisplayModes: ['inline']},
{autoResize: true},
);
function applyToolResult(result: CallToolResult): void {
for (const block of result.content ?? []) {
if (
block.type === 'resource' &&
'text' in block.resource &&
(block.resource.mimeType === A2UI_MIME_TYPE ||
block.resource.mimeType === 'application/json+a2ui')
) {
const messages = JSON.parse(block.resource.text) as ProcessableMessagePayload;
processor.processMessages(messages);
}
}
}
const processor = new MessageProcessor([basicCatalog], async action => {
const result = await app.callServerTool({
name: action.name,
arguments: action.context,
});
applyToolResult(result);
});
processor.onSurfaceCreated(surface => {
render(html``, rootEl);
});
app.ontoolresult = params => {
applyToolResult(params);
};
await app.connect();
```
If you need to include full form state from the surface `DataModel` alongside explicit `action.context` entries (as the Editor sample does when submitting slider and checkbox adjustments to `smart_editor_apply`), read `processor.getSurface(action.surfaceId)?.dataModel.get('/')` inside the action callback and merge it into `arguments`. You can also speak the underlying JSON-RPC 2.0 `window.parent.postMessage` protocol directly without `@modelcontextprotocol/ext-apps`; see [`server/apps/src/src/main.ts`](https://github.com/a2ui-project/a2ui/blob/main/samples/community/mcp/a2ui-in-mcpapps/server/apps/src/src/main.ts) for a zero-wrapper reference.
### Step 3: Bundle into a single self-contained HTML file
MCP Apps load inside a `srcdoc` iframe with `sandbox="allow-scripts"` and no base server URL, so external `