Skip to main content
Agent-to-Agent Protocol を使って AdCP を統合するためのトランスポート別ガイドです。タスク処理、ステータス管理、ワークフローパターンは Task Lifecycle を参照してください。

A2A クライアントのセットアップ

1. A2A クライアントを初期化

const a2a = new A2AClient({
  endpoint: 'https://adcp.example.com/a2a',
  apiKey: process.env.ADCP_API_KEY,
  agent: {
    name: "AdCP Media Buyer",
    version: "1.0.0"
  }
});

2. エージェントカードを確認

// Check available skills
const agentCard = await a2a.getAgentCard();
console.log(agentCard.skills.map(s => s.name));
// ["get_products", "create_media_buy", "sync_creatives", ...]

3. 最初のタスクを送る

const response = await a2a.send({
  message: {
    parts: [{
      kind: "text",
      text: "Find video products for pet food campaign"
    }]
  }
});

// すべてのレスポンスに統一ステータスフィールドが含まれる(AdCP 1.6.0+)  
console.log(response.status);   // "completed" | "input-required" | "working" | etc.
console.log(response.message);  // Human-readable summary

メッセージ構造(A2A 固有)

マルチパートメッセージ

A2A の強みは、テキスト・データ・ファイルを組み合わせたマルチパートメッセージです:
// Text + structured data + file
const response = await a2a.send({
  message: {
    parts: [
      {
        kind: "text",
        text: "Create campaign with these assets"
      },
      {
        kind: "data", 
        data: {
          skill: "create_media_buy",
          parameters: {
            packages: ["pkg_001"],
            total_budget: 100000
          }
        }
      },
      {
        kind: "file",
        uri: "https://cdn.example.com/hero-video.mp4",
        name: "hero_video_30s.mp4"
      }
    ]
  }
});

スキル呼び出し方法

自然言語(柔軟)

// Agent interprets intent
const task = await a2a.send({
  message: {
    parts: [{
      kind: "text",
      text: "Find premium CTV inventory under $50 CPM"
    }]
  }
});

明示的スキル(決定的)

// Explicit skill with exact parameters
const task = await a2a.send({
  message: {
    parts: [{
      kind: "data",
      data: {
        skill: "get_products",
        parameters: {
          max_cpm: 50,
          format_types: ["video"],
          tier: "premium"
        }
      }
    }]
  }
});

ハイブリッド(推奨)

// Context + explicit execution for best results
const task = await a2a.send({
  message: {
    parts: [
      {
        kind: "text",
        text: "Looking for inventory for spring campaign targeting millennials"
      },
      {
        kind: "data", 
        data: {
          skill: "get_products",
          parameters: {
            audience: "millennials",
            season: "Q2_2024",
            max_cpm: 45
          }
        }
      }
    ]
  }
});
ステータス処理: 完全なパターンは Task Lifecycle を参照してください。

A2A レスポンス形式

AdCP 1.6.0 の新機能: すべてのレスポンスに統一ステータスフィールドが含まれます。

標準レスポンス構造

A2A 上の AdCP レスポンスは、タスクレスポンスを含む DataPart(kind: ‘data’)を少なくとも 1 つ含める 必要があります。人間向けメッセージの TextPart(kind: ‘text’)は 推奨 ですが任意です。
{
  "status": "completed",        // Unified status (see Core Concepts)
  "taskId": "task-123",         // A2A task identifier
  "contextId": "ctx-456",       // Automatic context management
  "artifacts": [{               // A2A-specific artifact structure
    "artifactId": "artifact-product-catalog-abc",
    "name": "product_catalog",
    "parts": [
      {
        "kind": "text",          // Optional but recommended
        "text": "Found 12 video products perfect for pet food campaigns"
      },
      {
        "kind": "data",          // Required - contains AdCP response payload
        "data": {
          "products": [...],
          "total": 12
        }
      }
    ]
  }]
}
完全な標準仕様は A2A Response Format を参照してください。

A2A 固有フィールド

  • taskId: ストリーミング更新のための A2A タスク ID
  • contextId: A2A プロトコルが自動管理
  • artifacts: テキスト・データを含むマルチパート成果物
  • status: 一貫性のため MCP と同じ値(A2A TaskState enum)

アーティファクトの処理

DataPart が複数ある場合(ストリーミングなど)は 最後の DataPart を正とします:
// アーティファクトを抽出(現状 AdCP は 1 レスポンス 1 アーティファクト)
const artifact = response.artifacts?.[0];

if (artifact) {
  const message = artifact.parts?.find(p => p.kind === 'text')?.text;
  const data = artifact.parts?.find(p => p.kind === 'data')?.data;

  return {
    artifactId: artifact.artifactId,
    message,
    data,
    status: response.status
  };
}

return { status: response.status };
レスポンス構造の要件、エラーハンドリング、実装パターンの詳細は A2A Response Format を参照してください。

プッシュ通知(A2A 固有)

A2A では PushNotificationConfig によりプッシュ通知が標準で定義されています。Webhook URL を設定すると、ポーリング不要でサーバーがタスク更新を直接 POST します。

ベストプラクティス: URL ベースのルーティング

推奨: ルーティング情報(task_type, operation_id)はペイロードではなく Webhook URL に含める。 理由:
  • 業界標準パターン - 多くの API で採用
  • 関心の分離 - ルーティングは URL、データはペイロード
  • プロトコル非依存 - MCP/A2A/REST などで共通に使える
  • ハンドラー簡潔化 - ペイロード解析ではなく URL でルーティング
URL Pattern Options:
// Option 1: Path parameters (recommended)
url: `https://buyer.com/webhooks/a2a/${taskType}/${operationId}`
// Example: /webhooks/a2a/create_media_buy/op_nike_q1_2025

// Option 2: Query parameters
url: `https://buyer.com/webhooks/a2a?task=${taskType}&op=${operationId}`

// Option 3: Subdomain routing
url: `https://${taskType}.webhooks.buyer.com/${operationId}`
設定例:
const operationId = "op_nike_q1_2025";
const taskType = "create_media_buy";

await a2a.send({
  message: {
    parts: [{
      kind: "data",
      data: {
        skill: "create_media_buy",
        parameters: { /* task params */ }
      }
    }]
  },
  pushNotificationConfig: {
    url: `https://buyer.com/webhooks/a2a/${taskType}/${operationId}`,
    token: "client-validation-token",  // 任意: クライアント側検証用
    authentication: {
      schemes: ["bearer"],
      credentials: "shared_secret_32_chars"
    }
  }
});
Webhook のペイロード形式、プロトコル比較、詳細な処理例は Webhooks を参照してください。

SSE ストリーミング(A2A 固有)

A2A の強みは Server-Sent Events によるリアルタイム更新です:

タスク監視

class A2aTaskMonitor {
  constructor(taskId) {
    this.taskId = taskId;
    this.events = new EventSource(`/a2a/tasks/${taskId}/events`);
    
    this.events.addEventListener('status', (e) => {
      const update = JSON.parse(e.data);
      this.handleStatusUpdate(update);
    });
    
    this.events.addEventListener('progress', (e) => {
      const data = JSON.parse(e.data);
      console.log(`${data.percentage}% - ${data.message}`);
    });
  }
  
  handleStatusUpdate(update) {
    switch (update.status) {
      case 'input-required':
        // 追加情報・承認が必要
        this.emit('input-required', update);
        break;
      case 'completed':
        this.events.close();
        this.emit('completed', update);
        break;
      case 'failed':
        this.events.close();
        this.emit('failed', update);
        break;
    }
  }
}

リアルタイム更新の例

// 長時間オペレーションを開始
const response = await a2a.send({
  message: {
    parts: [{
      kind: "data",
      data: {
        skill: "create_media_buy",
        parameters: { packages: ["pkg_001"], total_budget: 100000 }
      }
    }]
  }
});

// SSE でリアルタイム監視
if (response.status === 'working' || response.status === 'submitted') {
  const monitor = new A2aTaskMonitor(response.taskId);
  
  monitor.on('progress', (data) => {
    updateUI(`${data.percentage}%: ${data.message}`);
  });
  
  monitor.on('completed', (final) => {
    console.log('Created:', final.artifacts[0].parts[1].data.media_buy_id);
  });
}

A2A Webhook ペイロード例

例 1: 完了時の Task ペイロード タスク完了時、サーバーは タスク結果を .artifacts に含む 完全な Task オブジェクトを送信します:
{
  "id": "task_456",
  "contextId": "ctx_123",
  "status": {
    "state": "completed",
    "timestamp": "2025-01-22T10:30:00Z"
  },
  "artifacts": [{
    "name": "task_result",
    "parts": [
      {
        "kind": "text",
        "text": "Media buy created successfully"
      },
      {
        "kind": "data",
        "data": {
          "media_buy_id": "mb_12345",
          "buyer_ref": "nike_q1_campaign",
          "creative_deadline": "2024-01-30T23:59:59Z",
          "packages": [
            { "package_id": "pkg_001", "buyer_ref": "nike_ctv_package" }
          ]
        }
      }
    ]
  }]
}
重要: completed または failed ステータスでは、AdCP タスク結果は status.message.parts[] ではなく .artifacts[0].parts[] に必ず入れる必要があります 例 2: 進捗更新用 TaskStatusUpdateEvent 実行中の中間ステータス更新では、status.message.parts[] に任意データを含められます:
{
  "taskId": "task_456",
  "contextId": "ctx_123",
  "status": {
    "state": "input-required",
    "message": {
      "role": "agent",
      "parts": [
        { "text": "Campaign budget $150K requires VP approval" },
        {
          "data": {
            "reason": "BUDGET_EXCEEDS_LIMIT"
          }
        }
      ]
    },
    "timestamp": "2025-01-22T10:15:00Z"
  }
}
すべてのステータスペイロードは AdCP スキーマを使用します: 最終ステータス(completed/failed)も中間ステータス(working, input-required, submitted)も async-response-data.json に参照がある対応スキーマを持ちます。中間ステータスのスキーマは策定中で将来変更される可能性があるため、実装者は緩めに扱う選択も可能です。

A2A Webhook のペイロード種別

Per the A2A specification, the server sends different payload types based on the situation:
Payload TypeWhen UsedWhat It Contains
Task最終状態(completed, failed, canceled)や完全なコンテキストが必要な場合履歴とアーティファクトデータを含む完全なタスクオブジェクト
TaskStatusUpdateEvent実行中のステータス遷移(working, input-requiredメッセージパートを含む軽量ステータス更新
TaskArtifactUpdateEventストリーミングによるアーティファクト更新利用可能になったアーティファクトデータ
AdCP では主に次の 2 つが多くなります:
  • Task: 最終結果(completed, failed
  • TaskStatusUpdateEvent: 進捗更新(working, input-required

Webhook が送信される条件

Webhooks are sent when all of these conditions are met:
  1. Task type supports async (e.g., create_media_buy, sync_creatives, get_products)
  2. pushNotificationConfig is provided in the request
  3. Task runs asynchronously — initial response is working or submitted
初回レスポンスがすでに終端(completed, failed, rejected)なら Webhook は送信されません。結果はその場で得られます。 Webhook を送るステータス変化:
  • working → 進捗更新(処理中)
  • input-required → 人による入力が必要
  • completed → 最終結果
  • failed → エラー詳細
  • canceled → キャンセル確定

データスキーマのバリデーション

A2A Webhook の status.message.parts[].data フィールドはステータス別スキーマを使用します:
StatusSchemaContents
completed[task]-response.jsonFull task response (success branch)
failed[task]-response.jsonFull task response (error branch)
working[task]-async-response-working.jsonProgress info (percentage, step)
input-required[task]-async-response-input-required.jsonRequirements, approval data
submitted[task]-async-response-submitted.jsonAcknowledgment (usually minimal)
スキーマ参照: async-response-data.json

Webhook ハンドラーの例

const express = require('express');
const app = express();

app.post('/webhooks/a2a/:taskType/:operationId', async (req, res) => {
  const { taskType, operationId } = req.params;
  const webhook = req.body;

  // Webhook の正当性検証(Bearer トークン例)
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing Authorization header' });
  }
  const token = authHeader.substring(7);
  if (token !== process.env.A2A_WEBHOOK_TOKEN) {
    return res.status(401).json({ error: 'Invalid token' });
  }

  // A2A Webhook ペイロードから基本フィールドを抽出
  const taskId = webhook.id || webhook.taskId;
  const contextId = webhook.contextId;
  const status = webhook.status?.state || webhook.status;

  // ステータスに応じて AdCP データを抽出
  let adcpData, textMessage;

  if (status === 'completed' || status === 'failed') {
    // FINAL STATES: Extract from .artifacts
    const dataPart = webhook.artifacts?.[0]?.parts?.find(p => p.kind === 'data');
    const textPart = webhook.artifacts?.[0]?.parts?.find(p => p.kind === 'text');
    adcpData = dataPart?.data;
    textMessage = textPart?.text;
  } else {
    // INTERIM STATES: Extract from status.message.parts (optional)
    const dataPart = webhook.status?.message?.parts?.find(p => p.data);
    const textPart = webhook.status?.message?.parts?.find(p => p.text);
    adcpData = dataPart?.data;
    textMessage = textPart?.text;
  }

  // ステータスに応じた処理
  switch (status) {
    case 'input-required':
      // 人に入力が必要であることを通知
      await notifyHuman({
        task_id: taskId,
        context_id: contextId,
        message: textMessage,
        data: adcpData
      });
      break;

    case 'completed':
      // 完了したオペレーションを処理
      if (adcpData?.media_buy_id) {
        await handleMediaBuyCreated({
          media_buy_id: adcpData.media_buy_id,
          buyer_ref: adcpData.buyer_ref,
          packages: adcpData.packages
        });
      }
      break;

    case 'failed':
      // 失敗を処理
      await handleOperationFailed({
        task_id: taskId,
        error: adcpData?.errors,
        message: textMessage
      });
      break;

    case 'working':
      // 進捗 UI を更新
      await updateProgress({
        task_id: taskId,
        percentage: adcpData?.percentage,
        message: textMessage
      });
      break;

    case 'canceled':
      await handleOperationCanceled(taskId);
      break;
  }

  // 正常処理時は必ず 200 を返す
  res.status(200).json({ status: 'processed' });
});

コンテキスト管理(A2A 固有)

主要な利点: A2A はコンテキストを自動管理するため、context_id を手動で扱う必要はありません。

自動コンテキスト

// 最初のリクエスト - A2A が自動でコンテキストを作成
const response1 = await a2a.send({
  message: {
    parts: [{ kind: "text", text: "Find premium video products" }]
  }
});

// 後続リクエスト - A2A が自動でコンテキストを保持  
const response2 = await a2a.send({
  message: {
    parts: [{ kind: "text", text: "Filter for sports content" }]
  }
});
// システムが自動で前回のリクエストに紐づける

明示的コンテキスト(任意)

// 明示的に制御したい場合
const response2 = await a2a.send({
  contextId: response1.contextId,  // 任意 - A2A が追跡済み
  message: {
    parts: [{ kind: "text", text: "Refine those results" }]
  }
});
MCP との違い: MCP の手動 context_id 管理と異なり、A2A はプロトコルレベルでセッション継続を扱います。

マルチモーダルメッセージ(A2A 固有)

A2A の特徴は、1 つのメッセージ内にテキスト・データ・ファイルを組み合わせられることです:

コンテキスト付きクリエイティブアップロード

// キャンペーンコンテキスト付きでクリエイティブを送信
const response = await a2a.send({
  message: {
    parts: [
      {
        kind: "text",
        text: "Add this hero video to the premium sports campaign"
      },
      {
        kind: "data",
        data: {
          skill: "sync_creatives",
          parameters: {
            media_buy_id: "mb_12345",
            action: "upload_and_assign"
          }
        }
      },
      {
        kind: "file",
        uri: "https://cdn.example.com/hero-30s.mp4",
        name: "sports_hero_30s.mp4"
      }
    ]
  }
});

キャンペーンブリーフ + アセット

// 完全なキャンペーンブリーフを送信
await a2a.send({
  message: {
    parts: [
      {
        kind: "text",
        text: "Campaign brief and assets for Q1 launch"
      },
      {
        kind: "file",
        uri: "https://docs.google.com/campaign-brief.pdf",
        name: "Q1_campaign_brief.pdf"
      },
      {
        kind: "data",
        data: {
          budget: 250000,
          kpis: ["reach", "awareness", "conversions"],
          target_launch: "2024-01-15"
        }
      }
    ]
  }
});

利用可能なスキル

すべての AdCP タスクは A2A スキルとして利用できます。確実な実行には明示的な呼び出しを使用してください: タスク管理: 全ドメインにわたる非同期追跡、ポーリングパターン、Webhook 連携の詳細は Webhooks を参照。

スキルの構造

// Standard pattern for explicit skill invocation
await a2a.send({
  message: {
    parts: [{
      kind: "data",
      data: {
        skill: "skill_name",        // Exact name from Agent Card
        parameters: {              // Task-specific parameters
          // See task documentation for parameters
        }
      }
    }]
  }
});

利用可能なスキル

  • Protocol: get_adcp_capabilities (start here to discover agent capabilities)
  • Media Buy: get_products, list_creative_formats, create_media_buy, update_media_buy, sync_creatives, get_media_buy_delivery, provide_performance_feedback
  • Signals: get_signals, activate_signal
タスクパラメータ: 詳細なパラメータ仕様は Media BuySignals を参照してください。

エージェントカード

A2A エージェントは .well-known/agent.json の Agent Card で機能を公開します:

Agent Card の取得

// エージェントの機能を取得
const agentCard = await a2a.getAgentCard();

// 利用可能なスキルを列挙
const skillNames = agentCard.skills.map(skill => skill.name);
console.log('Available skills:', skillNames);

// スキル詳細を取得
const getProductsSkill = agentCard.skills.find(s => s.name === 'get_products');
console.log('Examples:', getProductsSkill.examples);

Agent Card の例

{
  "name": "AdCP Media Buy Agent",
  "description": "AI-powered media buying agent",
  "skills": [
    {
      "name": "get_products",
      "description": "Discover available advertising products",
      "examples": [
        "Find premium CTV inventory for sports fans",
        "Show me video products under $50 CPM"
      ]
    }
  ],
  "extensions": [
    {
      "uri": "https://adcontextprotocol.org/extensions/adcp",
      "description": "AdCP media buying protocol support",
      "required": false,
      "params": {
        "adcp_version": "2.6.0",
        "protocols_supported": ["media_buy"],
        "extensions_supported": ["sustainability"]
      }
    }
  ]
}

AdCP 拡張

推奨: 実行時の機能発見には get_adcp_capabilities を使用してください。エージェントカードの拡張は、レジストリやディスカバリーサービス向けの静的メタデータを提供します。
extensions 配列に AdCP 拡張を含めることで、プログラム的に AdCP 対応を宣言できます。 A2A プロトコルでは extensions 配列に以下を持つ拡張を列挙します:
  • uri: 拡張の識別子(https://adcontextprotocol.org/extensions/adcp を使用)
  • description: AdCP をどう使うかの説明
  • required: クライアントがこの拡張を必須とするか(AdCP は通常 false
  • params: AdCP 固有の設定(下記スキーマ参照)
// エージェントが AdCP に対応しているか確認
const agentCard = await fetch('https://sales.example.com/.well-known/agent.json')
  .then(r => r.json());

// extensions 配列から AdCP 拡張を取得
const adcpExt = agentCard.extensions?.find(
  ext => ext.uri === 'https://adcontextprotocol.org/extensions/adcp'
);

if (adcpExt) {
  console.log('AdCP Version:', adcpExt.params.adcp_version);
  console.log('Supported domains:', adcpExt.params.protocols_supported);
  // ["media_buy", "creative", "signals"]
  console.log('Typed extensions:', adcpExt.params.extensions_supported);
  // ["sustainability"]
}
Extension Params: v2 では adcp-extension.json スキーマが使われていましたが、v3 で廃止されました。v3 以降のエージェントでは get_adcp_capabilities タスクで実行時に機能を発見してください。上記の params オブジェクトは典型的な構造です。 メリット:
  • テストコールなしで AdCP 対応状況を発見できる
  • 実装しているプロトコルドメイン(media_buy, creative, signals)を宣言できる
  • バージョンに基づく互換性チェックが可能

統合の例

// A2A クライアントを初期化  
const a2a = new A2AClient({ /* config */ });

// 統一ステータスで処理(Core Concepts を参照)
async function handleA2aResponse(response) {
  switch (response.status) {
    case 'input-required':
      // 追加情報要求を処理(パターンは Core Concepts 参照)
      const input = await promptUser(response.message);
      return a2a.send({
        contextId: response.contextId,
        message: { parts: [{ kind: "text", text: input }] }
      });
      
    case 'working':
      // SSE ストリーミングで監視
      return streamUpdates(response.taskId);
      
    case 'completed':
      return response.artifacts[0].parts[1].data;
      
    case 'failed':
      throw new Error(response.message);
  }
}

// マルチモーダルメッセージによる使用例
const result = await a2a.send({
  message: {
    parts: [
      { kind: "text", text: "Find luxury car inventory" },
      { kind: "data", data: { skill: "get_products", parameters: { audience: "luxury car intenders" } } }
    ]
  }
});

const finalResult = await handleA2aResponse(result);

A2A 固有の考慮点

エラーハンドリング

// A2A トランスポートエラーとタスクエラーの違い
// タスク管理パターンの詳細は Task Management を参照
try {
  const response = await a2a.send(message);
  
  if (response.status === 'failed') {
    // AdCP タスクエラー - ユーザーに表示
    showError(response.message);
  }
} catch (a2aError) {
  // A2A トランスポートエラー(接続、認証など)
  console.error('A2A Error:', a2aError);
}

クリエイティブアップロードのエラーハンドリング

For uploading creative assets and handling validation errors, use the sync_creatives task. See sync_creatives Task Reference for complete testable examples. @adcp/client ライブラリは A2A アーティファクトの抽出を自動で処理するため、レスポンス構造を手動で解析する必要はありません。

ベストプラクティス

  1. ハイブリッドメッセージ(テキスト + データ + 必要に応じてファイル)を活用
  2. アーティファクト処理前に status フィールド を確認
  3. 長時間処理には SSE ストリーミング でリアルタイム更新
  4. ステータス処理パターンは Core Concepts を参照
  5. 利用可能なスキルと例は エージェントカード で確認

次のステップ

ステータス処理、非同期オペレーション、確認フローについては Task Lifecycle を参照してください。このガイドは A2A トランスポート固有の内容に絞っています。