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

MCP 経由で AdCP をテスト

testing.adcontextprotocol.org のリファレンス実装で AdCP タスクをテストできます。このエンドポイントはすべての AdCP タスクを MCP ツールとして実装しており、開発や統合テストに有用です。

ツールコールパターン

基本のツール呼び出し

// Standard MCP tool call
const response = await mcp.call('get_products', {
  brand_manifest: {
    name: "Premium Pet Foods",
    url: "https://premiumpetfoods.com"
  },
  brief: "Video campaign for pet owners"
});

// All responses include status field (AdCP 1.6.0+)
console.log(response.status);   // "completed" | "input-required" | "working" | etc.
console.log(response.message);  // Human-readable summary

フィルター付きツール呼び出し

// Structured parameters
const response = await mcp.call('get_products', {
  brand_manifest: {
    name: "BetNow",
    url: "https://betnow.com"
  },
  brief: "Sports betting app for March Madness",
  filters: {
    format_types: ["video"],
    delivery_type: "guaranteed",
    max_cpm: 50
  }
});

アプリケーションレベルのコンテキスト付き呼び出し

// Pass opaque application-level context; agents must carry it back
const response = await mcp.call('build_creative', {
  target_format_id: { agent_url: 'https://creative.agent', id: 'premium_bespoke_display' },
  creative_manifest: { /* ... */ },
  context: { ui: 'buyer_dashboard', session: '123' }
});

// Response includes the same context at the top level
console.log(response.context); // { ui: 'buyer_dashboard', session: '123' }

MCP レスポンス形式

AdCP 1.6.0 の新機能: すべてのレスポンスに統一ステータスフィールドが含まれます。 MCP レスポンスは フラット構造 で、タスク固有フィールドがプロトコルフィールドと同じトップレベルに配置されます:
{
  "status": "completed",           // Unified status (see Core Concepts)
  "message": "Found 5 products",   // Human-readable summary
  "context_id": "ctx-abc123",      // MCP session continuity
  "context": { "ui": "buyer_dashboard" }, // Application-level context echoed back
  "products": [...],               // Task-specific data (flat, not nested)
  "errors": [...]                  // Task-level errors/warnings
}

MCP 固有フィールド

  • context_id: 手動で管理するセッション ID
  • context: 呼び出し元が提供し、エージェントがそのまま返す不透明メタデータ
  • status: 一貫性のため A2A と同じ値
  • タスク固有フィールド(例: products, media_buy_id, creatives)は data にラップされずトップレベルに配置
ステータス処理: 完全なパターンは Task Lifecycle を参照してください。

利用可能なツール

すべての AdCP タスクは MCP ツールとして利用できます:

プロトコルツール

await mcp.call('get_adcp_capabilities', {...});  // Discover agent capabilities (start here)

Media Buy ツール

await mcp.call('get_products', {...});           // Discover inventory
await mcp.call('list_creative_formats', {...});  // Get format specs
await mcp.call('create_media_buy', {...});       // Create campaigns
await mcp.call('update_media_buy', {...});       // Modify campaigns
await mcp.call('sync_creatives', {...});         // Manage creative assets
await mcp.call('get_media_buy_delivery', {...}); // Performance metrics
await mcp.call('provide_performance_feedback', {...}); // Share outcomes

タスク管理ツール

await mcp.call('tasks/list', {...});          // List and filter async tasks
await mcp.call('tasks/get', {...});           // Poll specific task status

Signals ツール

await mcp.call('get_signals', {...});      // Discover audience signals
await mcp.call('activate_signal', {...});  // Deploy signals to platforms
タスクパラメータ: Media Buy および Signals セクションの各タスクドキュメントを参照。 タスク管理: 非同期追跡、ポーリングパターン、Webhook 連携の詳細は Webhooks を参照。

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

重要: MCP はコンテキストを手動管理する必要があります。会話状態を保つには context_id を渡してください。

コンテキストセッションパターン

class McpAdcpSession {
  constructor(mcpClient) {
    this.mcp = mcpClient;
    this.contextId = null;
    this.activeTasks = new Map(); // Track async operations
  }
  
  async call(tool, params, options = {}) {
    // Build request with protocol-level fields
    const request = {
      tool: tool,
      arguments: params
    };
    
    // Include context from previous calls
    if (this.contextId) {
      request.context_id = this.contextId;
    }
    
    // Include webhook configuration (protocol-level, A2A-compatible)
    if (options.push_notification_config) {
      request.push_notification_config = options.push_notification_config;
    }
    
    const response = await this.mcp.call(request);
    
    // Save context for next call
    this.contextId = response.context_id;
    
    // Track async operations
    if (response.task_id) {
      this.activeTasks.set(response.task_id, {
        tool,
        params,
        startTime: new Date(),
        status: response.status
      });
    }
    
    return response;
  }
  
  reset() {
    this.contextId = null;
    this.activeTasks.clear();
  }
  
  // Poll specific task
  async pollTask(taskId, includeResult = false) {
    return this.call('tasks/get', { 
      task_id: taskId, 
      include_result: includeResult 
    });
  }
  
  // List pending tasks
  async listPendingTasks() {
    return this.call('tasks/list', {
      filters: {
        statuses: ["submitted", "working", "input-required"]
      }
    });
  }
  
  // State reconciliation helper
  async reconcileState() {
    const pending = await this.listPendingTasks();
    const serverTasks = new Set(pending.tasks.map(t => t.task_id));
    const clientTasks = new Set(this.activeTasks.keys());
    
    return {
      missing_from_client: [...serverTasks].filter(id => !clientTasks.has(id)),
      missing_from_server: [...clientTasks].filter(id => !serverTasks.has(id)),
      total_pending: pending.tasks.length
    };
  }
}

使用例

基本的なコンテキスト付きセッション

const session = new McpAdcpSession(mcp);

// First call - no context needed
const products = await session.call('get_products', {
  brief: "Sports campaign"
});

// Follow-up - context automatically included
const refined = await session.call('get_products', {
  brief: "Focus on premium CTV"
});
// Session remembers previous interaction

Webhook を用いた非同期処理

MCP はプッシュ通知を定義していません。AdCP は Webhook 設定(pushNotificationConfig)とペイロード形式(mcp-webhook-payload.json)を規定することでこのギャップを埋めます。Webhook を設定すると、サーバーはポーリング不要でタスク更新を URL に POST します。 Webhook Envelope: mcp-webhook-payload.json
ベストプラクティス: 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/adcp/${taskType}/${operationId}`
// Example: /webhooks/adcp/create_media_buy/op_nike_q1_2025

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

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

// Configure webhook with routing in URL
const response = await session.call('create_media_buy',
  {
    buyer_ref: "nike_q1_2025",
    packages: [...],
    budget: { total: 150000, currency: "USD" }
  },
  {
    pushNotificationConfig: {
      url: `https://buyer.com/webhooks/adcp/${taskType}/${operationId}`,
      authentication: {
        schemes: ["HMAC-SHA256"],  // or ["bearer"] for simple auth
        credentials: "shared_secret_32_chars"
      }
    }
  }
);

if (response.status === 'submitted') {
  console.log(`Task ${response.task_id} submitted for long-running execution`);
  // Server will POST status updates to your webhook URL
} else if (response.status === 'completed') {
  console.log(`Media buy created: ${response.media_buy_id}`);
}
Webhook POST format:
{
  "task_id": "task_456",
  "status": "completed",
  "timestamp": "2025-01-22T10:30:00Z",
  "result": {
    "media_buy_id": "mb_12345",
    "buyer_ref": "nike_q1_2025",
    "packages": [...]
  }
}
Note: この例は task_typeoperation_id を URL に載せる推奨パターン(例: /webhooks/adcp/create_media_buy/op_456)に従っています。後方互換のためペイロード内のフィールドもスキーマ上はサポートしていますが非推奨です。 result フィールドには AdCP のデータペイロードが入ります。completed/failed ではタスクレスポンス全体(例: create-media-buy-response.json)、それ以外のステータスではステータス別スキーマ(例: create-media-buy-async-response-working.json)を使用します。

MCP Webhook のエンベロープフィールド

mcp-webhook-payload.json には以下が含まれます: 必須フィールド:
  • task_id — 相関用の一意なタスク ID
  • status — 現在のタスクステータス(completed, failed, working, input-required など)
  • timestamp — Webhook 生成時の ISO 8601 タイムスタンプ
任意フィールド:
  • domain — AdCP ドメイン(“media-buy” または “signals”)
  • context_id — 会話/セッション ID
  • message — ステータス変更に関する人間向けコンテキスト
非推奨フィールド(サポートはするが推奨しない):
  • task_type — タスク名(例: “create_media_buy”, “sync_creatives”)- ⚠️ 非推奨: URL ベースのルーティング を参照
  • operation_id — 同一オペレーションの更新を関連付ける - ⚠️ 非推奨: 同上
Data フィールド:
  • result — タスク固有の AdCP ペイロード(下記のデータスキーマ検証を参照)

Webhook が送信される条件

Webhook は次の すべて を満たす場合に送信されます:
  1. タスクが非同期をサポート(例: create_media_buy, sync_creatives, get_products
  2. リクエストに pushNotificationConfig が指定 されている
  3. タスクが非同期実行 — 初回レスポンスが working または submitted
初回レスポンスがすでに終端(completed, failed, rejected)なら、結果が手元にあるため Webhook は送信されません。 Webhook を送るステータス変化:
  • working → 進捗更新(処理中)
  • input-required → 人による入力が必要
  • completed → 最終結果
  • failed → エラー詳細

データスキーマの検証

MCP Webhook の result フィールドはステータス別スキーマを使用します:
StatusSchemaContents
completed[task]-response.json成功ブランチの完全なタスクレスポンス
failed[task]-response.jsonエラーブランチの完全なタスクレスポンス
working[task]-async-response-working.json進捗情報(percentage, step
input-required[task]-async-response-input-required.json必要事項、承認情報
submitted[task]-async-response-submitted.json受領通知(通常は最小限)
スキーマ参照: async-response-data.json

Webhook Handler Example

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

app.post('/webhooks/adcp/:task_type/:agent_id/:operation_id', async (req, res) => {
  const { task_type, agent_id, operation_id } = req.params;
  const webhook = req.body;

  // Verify webhook authenticity (HMAC-SHA256 example)
  const signature = req.headers['x-adcp-signature'];
  const timestamp = req.headers['x-adcp-timestamp'];
  if (!verifySignature(webhook, signature, timestamp)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Handle status changes
  switch (webhook.status) {
    case 'input-required':
      // Alert human that input is needed
      await notifyHuman({
        operation_id,
        message: webhook.message,
        context_id: webhook.context_id,
        data: webhook.result
      });
      break;

    case 'completed':
      // Process the completed operation
      if (task_type === 'create_media_buy') {
        await handleMediaBuyCreated({
          media_buy_id: webhook.result.media_buy_id,
          buyer_ref: webhook.result.buyer_ref,
          packages: webhook.result.packages
        });
      }
      break;

    case 'failed':
      // Handle failure
      await handleOperationFailed({
        operation_id,
        error: webhook.result?.errors,
        message: webhook.message
      });
      break;

    case 'working':
      // Update progress UI
      await updateProgress({
        operation_id,
        percentage: webhook.result?.percentage,
        message: webhook.message
      });
      break;

    case 'canceled':
      await handleOperationCanceled(operation_id, webhook.message);
      break;
  }

  // Always return 200 for successful processing
  res.status(200).json({ status: 'processed' });
});

function verifySignature(payload, signature, timestamp) {
  const crypto = require('crypto');
  const expectedSig = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(timestamp + JSON.stringify(payload))
    .digest('hex');
  return signature === `sha256=${expectedSig}`;
}

タスク管理とポーリング

// Check status of specific task
const taskStatus = await session.pollTask('task_456', true);
if (taskStatus.status === 'completed') {
  console.log('Result:', taskStatus.result);
}

// State reconciliation
const reconciliation = await session.reconcileState();
if (reconciliation.missing_from_client.length > 0) {
  console.log('Found orphaned tasks:', reconciliation.missing_from_client);
  // Start tracking these tasks
}

// List all pending operations
const pending = await session.listPendingTasks();
console.log(`${pending.tasks.length} operations in progress`);

コンテキスト期限切れの扱い

async function handleContextExpiration(session, tool, params) {
  try {
    return await session.call(tool, params);
  } catch (error) {
    if (error.message?.includes('context not found')) {
      // Context expired - start fresh
      session.reset();
      return session.call(tool, params);
    }
    throw error;
  }
}
主な違い: コンテキストを自動管理する A2A と異なり、MCP は context_id を明示的に扱う必要があります。

非同期処理の扱い

タスクが working または submitted を返す場合、更新を受け取る方法は 2 つあります:
ApproachBest ForTrade-offs
Pollingシンプルな統合、短時間タスク実装が簡単だが長時間待機に非効率
Webhooks本番システム、長時間タスク効率的だが公開エンドポイントが必要

オプション 1: ポーリング

tasks/get を使って定期的にステータスを確認します:
async function waitForCompletion(session, initialResponse) {
  if (!initialResponse.task_id) {
    return initialResponse; // Already completed
  }
  
  // 'working' は短時間で終わるため高頻度にポーリング
  // 'submitted' は数時間かかる場合があるため低頻度にポーリング
  let pollInterval = initialResponse.status === 'working' ? 5000 : 30000;
  
  while (true) {
    const response = await session.pollTask(initialResponse.task_id, true);
    
    if (['completed', 'failed', 'canceled'].includes(response.status)) {
      return response;
    }
    
    if (response.status === 'input-required') {
      const input = await promptUser(response.message);
      return session.call('create_media_buy', {
        context_id: response.context_id,
        additional_info: input
      });
    }
    
    pollInterval = response.status === 'working' ? 5000 : 30000;
    await new Promise(resolve => setTimeout(resolve, pollInterval));
  }
}

オプション 2: Webhook

Webhook URL を設定すると、サーバーが直接更新を POST します。ポーリング不要なので長時間タスクで効率的です。
const response = await session.call('create_media_buy',
  {
    buyer_ref: "nike_q1_2025",
    packages: [...],
    budget: { total: 150000, currency: "USD" }
  },
  {
    pushNotificationConfig: {
      url: "https://buyer.com/webhooks/adcp",
      authentication: {
        schemes: ["HMAC-SHA256"],
        credentials: "shared_secret_32_chars"
      }
    }
  }
);

// ステータスが 'submitted' の場合、サーバーが Webhook に更新を送る
// ポーリング不要。Webhook を待つだけでよい
Webhooks でペイロード形式と処理例を参照。

ステータス別の扱い

const initial = await session.call('create_media_buy', {
  buyer_ref: "nike_q1_2025",
  packages: [...],
  budget: { total: 100000, currency: "USD" }
});

switch (initial.status) {
  case 'completed':
    // 即時完了 - 非同期処理不要
    console.log('Created:', initial.media_buy_id);
    break;
    
  case 'working':
    // 2 分以内に完了見込み - ポーリングまたは待機
    console.log('Processing...');
    const final = await waitForCompletion(session, initial);
    console.log('Created:', final.result.media_buy_id);
    break;
    
  case 'submitted':
    // 長時間(数時間〜数日) - Webhook か低頻度ポーリング
    console.log(`Task ${initial.task_id} queued for approval`);
    // Webhook will notify when complete, or poll manually
    break;
    
  case 'input-required':
    // ユーザー入力待ち
    console.log('Need more info:', initial.message);
    break;
}

統合の例

// コンテキスト管理付きで MCP セッションを初期化
const session = new McpAdcpSession(mcp);

// 統一ステータスで処理(Core Concepts を参照)
async function handleAdcpCall(tool, params, options = {}) {
  const response = await session.call(tool, params, options);
  
  switch (response.status) {
    case 'input-required':
      // 追加情報を処理(パターンは Core Concepts 参照)
      const input = await promptUser(response.message);
      return session.call(tool, { ...params, additional_info: input });
      
    case 'working':
      // 短時間の非同期を処理 
      return waitForCompletion(session, response);
      
    case 'submitted':
      // 長時間の非同期を処理
      if (options.webhook_url) {
        console.log(`Task ${response.task_id} submitted, webhook will notify`);
        return { pending: true, task_id: response.task_id };
      } else {
        console.log(`Task ${response.task_id} submitted, polling...`);
        return waitForCompletion(session, response);
      }
      
    case 'completed':
      return response; // タスク固有フィールドはトップレベル
      
    case 'failed':
      throw new Error(response.message);
  }
}

// Example usage
const products = await handleAdcpCall('get_products', {
  brief: "CTV campaign for luxury cars"
});

MCP 固有の考慮点

ツールディスカバリー

// List available AdCP tools
const tools = await mcp.listTools();
const adcpTools = tools.filter(t => t.name.startsWith('adcp_') || 
  ['get_products', 'create_media_buy'].includes(t.name));

MCP サーバーカードによる AdCP 拡張

推奨: 実行時の機能発見には get_adcp_capabilities を使用してください。サーバーカード拡張はツールカタログやレジストリ向けの静的メタデータを提供します。
MCP サーバーは /.well-known/mcp.json(または /.well-known/server.json)のサーバーカードで AdCP 対応を宣言できます。AdCP 固有メタデータは adcontextprotocol.org 名前空間の _meta フィールドに記載します。
{
  "name": "io.adcontextprotocol/media-buy-agent",
  "version": "1.0.0",
  "title": "AdCP Media Buy Agent",
  "description": "AI-powered media buying agent implementing AdCP",
  "tools": [
    { "name": "get_products" },
    { "name": "create_media_buy" },
    { "name": "list_creative_formats" }
  ],
  "_meta": {
    "adcontextprotocol.org": {
      "adcp_version": "2.6.0",
      "protocols_supported": ["media_buy"],
      "extensions_supported": ["sustainability"]
    }
  }
}
AdCP 対応の検出:
// Check both possible locations for MCP server card
const serverCard = await fetch('https://sales.example.com/.well-known/mcp.json')
  .then(r => r.ok ? r.json() : null)
  .catch(() => null)
  || await fetch('https://sales.example.com/.well-known/server.json')
    .then(r => r.json());

// Check for AdCP metadata
const adcpMeta = serverCard?._meta?.['adcontextprotocol.org'];

if (adcpMeta) {
  console.log('AdCP Version:', adcpMeta.adcp_version);
  console.log('Supported domains:', adcpMeta.protocols_supported);
  // ["media_buy", "creative", "signals"]
  console.log('Typed extensions:', adcpMeta.extensions_supported);
  // ["sustainability"]
}
メリット:
  • テストコールなしで AdCP の対応状況を把握できる
  • 実装しているプロトコルドメイン(media_buy, creative, signals)を宣言できる
  • サポートする拡張を宣言できる(Context & Sessions 参照)
  • バージョンに基づく互換性チェックが可能
Note: _meta フィールドは MCP server.json spec に従い逆 DNS の名前空間を使用します。/.well-known/mcp.json/.well-known/server.json の両方をサポートしてください。

パラメータバリデーション

// MCP provides tool schemas for validation
const toolSchema = await mcp.getToolSchema('get_products');
// 呼び出し前にスキーマでバリデーション

エラーハンドリング

try {
  const response = await session.call('get_products', params);
} catch (mcpError) {
  // MCP トランスポートエラー(接続、認証など)
  console.error('MCP Error:', mcpError);
} 

// AdCP タスクエラーは response.status === 'failed' で返る

ベストプラクティス

  1. セッションラッパーを利用 してコンテキストを自動管理
  2. レスポンス処理前に status フィールド を確認
  3. コンテキスト期限切れ はリトライで丁寧に処理
  4. ステータス処理パターンは Core Concepts を参照
  5. 利用可能なら MCP ツールスキーマで パラメータ検証

次のステップ

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