Model Context Protocol (MCP) 上での A2UI¶
このガイドでは、Model Context Protocol (MCP) の Tools と Resources を使って、A2UI の宣言的構文でリッチな対話 UI を構築する方法を説明します。
サンプルは MCP Samples を参照してください。
カタログのネゴシエーション¶
サーバーがクライアントへ A2UI を送るには、まずプロトコルの相互サポートを確認し、どのカタログが利用できるかを決める必要があります。システム構成によって、この能力のネゴシエーションは 2 通りの方法で行えます。接続ハンドシェイク時に 1 回だけ行うか、メッセージごとに行うかです。
オプション A: MCP 初期化時のカタログハンドシェイク¶
MCP は状態を持つセッションプロトコルなので、最も効率がよいのは接続確立時に機能を 1 回だけ宣言する方法です。クライアントは、標準 initialize リクエストの capabilities オブジェクト(多くは experimental か custom のキー)に A2UI サポートを宣言します。サーバーはその状態をセッション中保持します。
Initialize リクエストの例:
{
"jsonrpc": "2.0",
"method": "initialize",
"id": "init-123",
"params": {
"protocolVersion": "2025-11-25",
"clientInfo": {
"name": "a2ui-enabled-client",
"version": "1.0.0"
},
"capabilities": {
"a2ui": {
"clientCapabilities": {
"v0.10": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_10/basic_catalog.json"
]
}
}
}
}
}
}
オプション B: 各 MCP メッセージでカタログを伝える(ステートレスサーバー向け)¶
MCP サーバーを完全にステートレスにしたい場合、クライアントは各 tool call リクエストの _meta フィールドで A2UI バージョンとカタログサポートを送れます。サーバーはそのメタデータを見て、応答 UI に使うカタログを即座に判断します。
Call リクエストのメタデータ例:
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": "id-123",
"params": {
"name": "generate_report",
"arguments": { "date": "2026-03-01" },
"_meta": {
"a2ui": {
"clientCapabilities": {
"v0.10": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_10/basic_catalog.json"
],
"inlineCatalogs": []
}
}
}
}
}
}
埋め込みリソースとして A2UI コンテンツを返す¶
Embedded Resource を使うと、Tool はサーバー側に保存や追跡を必要とせず、その応答に直接紐づく UI レイアウトを返せます。
- URI:
a2ui://プレフィックスと、分かりやすい識別子を使う必要があります(例:a2ui://training-plan-page) - MIME Type:
application/json+a2uiを使います。これにより MCP クライアントは生 JSON ではなく A2UI レンダラーへペイロードを渡します
Python 実装例¶
import mcp.types as types
@self.tool()
def get_hello_world_ui():
a2ui_payload = [
{
"version": "v0.10",
"createSurface": {
"surfaceId": "default",
"catalogId": "https://a2ui.org/specification/v0_10/basic_catalog.json"
}
},
{
"version": "v0.10",
"updateComponents": {
"surfaceId": "default",
"components": [
{
"id": "root",
"component": "Text",
"text": "Hello World!"
}
]
}
}
]
# A2UI を Embedded Resource として包む
a2ui_resource = types.EmbeddedResource(
type="resource",
resource=types.TextResourceContents(
uri="a2ui://training-plan-page",
mimeType="application/json+a2ui",
text=json.dumps(a2ui_payload),
)
)
text_content = types.TextContent(
type="text",
text="Here is your generated training plan summary..."
)
return types.CallToolResult(content=[text_content, a2ui_resource])
ユーザーアクションの扱い¶
Button のような対話的コンポーネントは、actions をサーバーへ送れます。
1. アクション付きの A2UI JSON¶
{
"id": "confirm-button",
"component": {
"Button": {
"child": "confirm-button-text",
"action": {
"event": {
"name": "confirm_booking",
"context": {
"start": "/dates/start",
"end": "/dates/end"
}
}
}
}
}
}
2. A2UI アクションの MCP ペイロード¶
ボタンが押されると、クライアントは /dates/start や /dates/end のような絶対・相対パスをサーフェスのバインディング状態に対して解決し、その内容を MCP tool call の引数へ変換します。
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": "id-456",
"params": {
"name": "action",
"arguments": {
"name": "confirm_booking",
"context": {
"start": "2026-03-20",
"end": "2026-03-25"
}
}
}
}
3. MCP サーバー側のアクションハンドラー¶
MCP サーバーは tool call を受け取り、対応するハンドラーを実行します。
@self.tool()
async def action(action_payload: Dict[str, Any]) -> Dict[str, Any]:
if action_payload["name"] == "confirm_booking":
return {"response": f"Booking confirmed for {action_payload['context']['start']} to {action_payload['context']['end']}."}
raise ValueError(f"Unknown action: {action_payload['name']}")
エラー処理¶
ユーザー操作と同様に、MCP サーバーはクライアントからのエラーも受け取れます。
1. A2UI エラーの MCP ペイロード¶
クライアントが A2UI ペイロードの処理に失敗した場合、エラー MCP ペイロードをサーバーへ送れます。
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": "id-789",
"params": {
"name": "error",
"arguments": {
"code": "INVALID_JSON",
"message": "Failed to parse A2UI payload.",
"surfaceId": "default",
}
}
}
2. エラーハンドラーの MCP サーバーツール¶
MCP サーバーは tool call を受け取り、対応するハンドラーを実行します。
@self.tool()
async def error(error_payload: Dict[str, Any]) -> Dict[str, Any]:
return {"response": f"Received A2UI error: {error_payload['error']}."}
音声化と可視性の制御¶
MCP の Resource Annotations を使うと、その後の assistant がバックエンドのペイロードを「読む」かどうかを制御できます。
a2ui_resource = types.EmbeddedResource(
type="resource",
resource=types.TextResourceContents(
uri="a2ui://training-plan-page",
mimeType="application/json+a2ui",
text=json.dumps(a2ui_payload)
),
# 生 JSON は LLM から隠し、UI だけをユーザーへ表示する
annotations=types.Annotations(audience=["user"])
)
- 空の audience: 要素はユーザーと LLM の両方に見える
- audience
user: ビュー画面へアイテムを表示するために必要