A2A プロトコルは AI エージェント間の連携を実現するために設計されたオープンな標準です。異なるチームや組織が開発した AI エージェントが相互に通信し、協力してタスクを実行することを可能にします。
Mastra は TypeScript で AI エージェントを構築するためのフレームワークであり、A2A プロトコルをサポートしています。Mastra のサーバーを構築することで自動的に A2A プロトコルに準拠したサーバーが立ち上がります。Mastra のクライアント SDK を使用することで、A2A プロトコルに準拠したサーバーと通信できます。これにより、A2A プロトコルの仕様に準拠したサーバーに対してエージェントの情報やタスクを要求できます。
この記事では Mastra を使用して A2A プロトコルに準拠したサーバーを構築し、Mastra のクライアント SDK を使用して A2A プロトコルの仕様に従い通信を行う方法を紹介します。
A2A サーバー
Mastra のプロジェクトを作成し、A2A に準拠したサーバーを立ち上げます。A2A プロトコルに対応するために特別な設定は必要ありません。以下のコマンドを実行して Mastra のプロジェクトを作成します。
npx create-mastra@latest
対話形式でプロジェクトの設定を行います。
What do you want to name your project? my-mastra-app
Choose components to install:
◯ Agents (recommended)
◯ Tools
◯ Workflows
Select default provider:
◯ OpenAI (recommended)
◯ Anthropic
◯ Groq
Would you like to include example code? No / Yes
Turn your IDE into a Mastra expert? (Installs MCP server)
◯ Skip for now
◯ Cursor
◯ Windsurf
選択した LLM プロバイダーに応じて必要な API キーを取得する必要があります。例えば Anthropic
を選択した場合には環境変数 ANTHROPIC_API_KEY
を設定します。
ANTHROPIC_API_KEY=<your-anthropic-api-key>
簡単なエージェントを作成しましょう。まずはエージェントが使用するツールを作成します。src/mastra/tools/weather-tool.ts
を作成し、以下のコードを追加します。
import { createTool } from "@mastra/core";
import { z } from "zod";
export const weatherTool = createTool({
id: "get-weather",
description: "Get the current weather for a given location.",
inputSchema: z.object({
location: z.string().describe("The location to get the weather for."),
}),
outputSchema: z.object({
temperature: z.number().describe("The current temperature in Celsius."),
condition: z
.string()
.describe("The current weather condition (e.g., sunny, rainy)."),
location: z
.string()
.describe("The location for which the weather is reported."),
}),
execute: async ({ context }) => {
// ダミーのデータを返す
const weatherData = {
location: context.location,
temperature: 25,
condition: "Sunny",
};
return weatherData;
},
});
この例では地点を指定して天気を取得する weatherTool
というツールを作成しています。
ツールは createTool
関数を使用して定義します。description
はツールの説明であり、エージェントがどのタイミングでツールを呼び出すべきかの判断に使用されるため、ツールが何をするのかを簡潔に記述することでエージェントの精度を向上させることができます。
inputSchema
と outputSchema
はそれぞれツールの入力と出力のスキーマを定義します。スキーマは zod を使用して定義します。このスキーマの情報もまたエージェントがツールを正しく呼び出すために必要な情報です。できる限り .describe
を使用してスキーマの各フィールドの説明を追加することをお勧めします。
execute
はツールが実行されたときに呼び出される関数であり、ここではダミーのデータを返すようにしています。
次に weatherTool
を使用するエージェントを作成します。天気の情報を参考にして旅行の計画を立てるエージェントを作成します。src/mastra/agents/travelAgent.ts
を作成し、以下のコードを追加します。
import { anthropic } from "@ai-sdk/anthropic";
import { Agent } from "@mastra/core/agent";
import { weatherTool } from "../tools/weather-tool";
export const travelAgent = new Agent({
name: "travel-agent",
instructions: `ユーザーの旅行の計画を手伝うエージェントです。
旅行の計画を立てるために、ユーザーの希望や条件を聞き出し、最適な旅行プランを提案します。
必ず旅行先の天気を確認したうえで、天候に応じた旅行プランを提案してください。`,
model: anthropic("claude-4-sonnet-20250514"),
tools: {
weatherTool,
},
});
src/mastra/index.ts
が Mastra のエントリーポイントです。ここでエージェントを登録します。
import { Mastra } from "@mastra/core";
import { travelAgent } from "./agents/travelAgent";
export const mastra = new Mastra({
agents: { travelAgent },
});
以下のコマンドを実行してサーバーを起動します。
npm run dev
http://localhost:4111 にアクセスすると Mastra のダッシュボードが表示されます。Agents タブを選択すると作成した travel-agent
が表示されていることが確認できます。
A2A クライアント
続いて A2A クライアントを作成します。A2A クライアントはユーザーに代わって A2A サーバーと通信し、エージェントの情報を取得したり、タスクを依頼したりする役割を果たします。クライアントは A2A プロトコルを使用して通信を開始します。
Mastra の Client SDK である @mastra/client-js
を使用して A2A クライアントを作成します。まずは新しいプロジェクトを作成します。
mkdir mastra-client-example
cd mastra-client-example
npm init -y
必要なパッケージをインストールします。
npm install @mastra/client-js
npm install --save-dev typescript tsx
package.json
の "type"
フィールドを "module"
に設定して ES モジュールとして動作するようにします。コードを実行するための scripts
セクションも追加します。
{
"type": "module",
"scripts": {
"dev": "tsx src/index.ts"
}
}
AgentCard を取得する
まずは AgentCard を取得する例を見てみましょう。AgentCard はサーバーが提供しているエージェントのスキルなどの情報を記述した JSON 形式のデータです。クライアントは AgentCard の情報を元に適切なエージェントを選択し、タスクを依頼します。
A2A の仕様では AgentCard
は /.well-known/agent.json
というパスで提供されることが推奨されていますが、Mastra では ./well-known/{agentId}/agent.json
というパスで提供されているようです。
src/index.ts
を作成し、以下のコードを追加します。
import { MastraClient } from "@mastra/client-js";
const client = new MastraClient({
baseUrl: "http://localhost:4111",
});
// agent の ID を指定する
// agent の ID は Mastra({ agents: { ... }}) のキー名と同じ
const a2a = client.getA2A("travelAgent");
// /.well-known/{agentId}/agent.json にアクセスして AgentCard を取得する
const agentCard = await a2a.getCard();
console.log("Agent Card:");
console.log(JSON.stringify(agentCard, null, 2));
MastraClient
を使用して Mastra サーバーとやり取りを行うクライアントを作成します。baseUrl
には先ほど起動した Mastra サーバーの URL を指定します。
getA2A
メソッドを使用して A2A
クラスのインスタンスを取得します。このインスタンスを使用して A2A プロトコルに準拠した通信を行います。getA2A
メソッドの引数にはエージェントの ID を指定します。先ほど作成した travelAgent
の ID を指定します。
getCard
メソッドを使用して AgentCard を取得します。このコードを実行すると以下のような出力が得られます。
{
name: 'travel-agent',
description: 'ユーザーの旅行の計画を手伝うエージェントです。\n' +
' 旅行の計画を立てるために、ユーザーの希望や条件を聞き出し、最適な旅行プランを提案します。\n' +
' 必ず旅行先の天気を確認したうえで、天候に応じた旅行プランを提案してください。',
url: '/a2a/travelAgent',
provider: { organization: 'Mastra', url: 'https://mastra.ai' },
version: '1.0',
capabilities: {
streaming: true,
pushNotifications: false,
stateTransitionHistory: false
},
defaultInputModes: [ 'text' ],
defaultOutputModes: [ 'text' ],
skills: [
{
id: 'weatherTool',
name: 'weatherTool',
description: 'Get the current weather for a given location.',
tags: [Array]
}
]
}
メッセージを送信する
A2A プロトコルではクライアントがサーバーにメッセージを送信することで通信が開始されます。サーバーはクライアントからメッセージを受け取るタスクを生成して応答します。
まずは同期的にメッセージを送信する例を見てみましょう。sendMessage
メソッドを使用してメッセージを送信します。このメソッドは message/send
RPC メソッドに対応しています。メッセージを送信する場合にはクライアントは一意の ID を生成して指定する必要があります。
import { MastraClient } from "@mastra/client-js";
const client = new MastraClient({
baseUrl: "http://localhost:4111",
});
const a2a = client.getA2A("travelAgent");
const id = crypto.randomUUID();
const response = await a2a.sendMessage({
id,
message: {
role: "user",
parts: [
{ type: "text", text: "一泊二日の箱根旅行のプランを提案してください。" },
],
},
});
サーバー側はリクエストを受け取るとタスクを作成して応答します。sendMessage
の戻り値には task
オブジェクトが含まれており、以下のような情報が含まれています。
{
id: '81503036-d1bc-4147-bee7-02452fac7b40',
status: {
state: 'completed',
timestamp: '2025-05-24T05:33:24.014Z',
message: [Object]
},
artifacts: []
}
同期的なメッセージ送信では、task.status.state
が completed
になるまで待機します。取りうるタスクの状態は https://google.github.io/A2A/specification/#71-messagesend で確認できます。
tasks.status.message
を確認するとサーバーのエージェントが生成した応答が含まれています。
const response = await a2a.sendMessage({
id,
message: {
role: "user",
parts: [
{ type: "text", text: "一泊二日の箱根旅行のプランを提案してください。" },
],
},
});
for (const part of response.task.status.message?.parts || []) {
if (part.type === "text") {
console.log(part.text);
}
}
このコードを実行すると以下のような出力が得られます。
箱根の現在の天気は晴れで気温25度と、とても良い天候ですね!この天気を活かした一泊二日の箱根旅行プランをご提案いたします。
## 🌸 箱根一泊二日旅行プラン
### **1日目**
**午前**
- **10:00** 新宿駅から小田急ロマンスカーで箱根湯本駅へ(約85分)
- **11:30** 箱根湯本駅到着、荷物を宿泊施設に預ける
- **12:00** 箱根湯本温泉街散策・お土産ショッピング
...
sendMessage
メソッドではタスクの実行時間が長い場合には task.status.state
が working
の状態で応答が返ってきます。その場合にはクライアントはポーリングしてタスクの状態が completed
になるまで待機する必要があります。タスクの状態をポーリングするためには tasks/get
メソッドを使用します。このメソッドは以前に開始されたタスクの状態を取得します。
Mastra のクライアント SDK では getTask
メソッドを使用してタスクの状態を取得します。
const id = crypto.randomUUID();
const response = await a2a.sendMessage({
id,
message: {
role: "user",
parts: [
{ type: "text", text: "一泊二日の箱根旅行のプランを提案してください。" },
],
},
});
let task = response.task;
if (task.status.state === "working") {
// タスクの状態が working の場合はポーリングして状態を取得する
console.log("Waiting for task to complete...");
while (task.status.state === "working") {
await new Promise((resolve) => setTimeout(resolve, 1000));
task = await a2a.getTask({ id: task.id });
}
}
for (const part of task.status.message?.parts || []) {
if (part.type === "text") {
console.log(part.text);
}
}
2025 年 5 月 24 日時点では sendMessage
が正しく動作しない Issue が報告されています。[BUG] A2A getTask returns undefined · Issue #4415 · mastra-ai/mastra
tasks/cancel
メソッドを使用してタスクをキャンセルすることもできます。Mastra のクライアント SDK では cancelTask
メソッドを使用します。
const id = crypto.randomUUID();
const response = await a2a.sendMessage({
id,
message: {
role: "user",
parts: [
{ type: "text", text: "一泊二日の箱根旅行のプランを提案してください。" },
],
},
});
const task = response.task;
// タスクをキャンセルする
await a2a.cancelTask({ id: task.id });
ストリーミング
A2A の仕様ではエージェントにメッセージを送信しタスクを開始した後、SSE (Server-Sent Events) を使用してタスクの更新をリアルタイムで受信するメソッドが定義されています(message/stream
)。このメソッドを使用する場合には AgentCard の capabilities.streaming
が true
である必要があります。
Mastra のクライアント SDK では sendAndSubscribe
メソッドを使用してストリーミングを行います。
import { MastraClient } from "@mastra/client-js";
const client = new MastraClient({
baseUrl: "http://localhost:4111",
});
const a2a = client.getA2A("travelAgent");
const id = crypto.randomUUID();
const response = await a2a.sendAndSubscribe({
id,
message: {
role: "user",
parts: [
{ type: "text", text: "一泊二日の箱根旅行のプランを提案してください。" },
],
},
});
// ReadableStream を使用してストリーミングを受信する
const reader = response.body?.getReader();
if (!reader) {
throw new Error("No reader available");
}
// 無限ループでストリーミングを受信する
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
const text = new TextDecoder().decode(value);
console.log(text);
}
sendAndSubscribe
の戻り値は fetch API の Response
オブジェクトです。body
プロパティを使用して ReadableStream
を取得し、ストリーミングを受信します。
コードを実行するとまずは state
が working
の状態で即座に応答が返ってきます。これはタスクはエージェントによってアクティブに処理されていて、クライアントはさらなる更新または終了状態を期待している可能性があることを示しています。AI エージェントによる応答が完了すると state
が completed
の状態で応答が返ってきます。
{"jsonrpc":"2.0","id":"12873278-1479-4142-b94d-16cfdf48c6af","result":{"state":"working","message":{"role":"agent","parts":[{"type":"text","text":"Generating response..."}]}}}
{"jsonrpc":"2.0","id":"12873278-1479-4142-b94d-16cfdf48c6af","result":{"id":"b0a75793-25ab-49f1-bed0-799b3940c29d","status":{"state":"completed","timestamp":"2025-05-24T06:01:41.183Z","message":{"role":"agent","parts":[{"type":"text","text":"箱根の現在の天気は晴れで気温25度と、とても良い天候ですね!この天気を活かした一泊二日の箱根旅行プランをご提案いたします。\n\n ..."}},"artifacts":[]}}}
まとめ
- Mastra は A2A プロトコルをサポートしており、特別な設定なしで A2A サーバーを立ち上げることができる
- Mastra のクライアント SDK を使用して A2A プロトコルに準拠した通信を行うことができる
getCard
メソッドを使用して AgentCard を取得できるsendMessage
メソッドを使用してエージェントにメッセージを送信し、タスクを開始できるsendAndSubscribe
メソッドを使用してストリーミングでタスクの更新を受信できる- タスクの状態をポーリングするためには
getTask
メソッドを使用できる - タスクをキャンセルするためには
cancelTask
メソッドを使用できる