> ## Documentation Index
> Fetch the complete documentation index at: https://adcp-docs-ja.pier1.co.jp/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP ガイド

> AdCP MCP 統合ガイド: Model Context Protocol 実装のためのツールコールパターン、context_id 管理、レスポンス解析、ワイヤフォーマット。

Model Context Protocol を使って AdCP を統合するためのトランスポート別ガイドです。タスク処理、ステータス管理、ワークフローパターンは [Task Lifecycle](/docs/building/by-layer/L3/task-lifecycle) を参照してください。

## MCP 経由で AdCP をテスト

[CLI ツール](/docs/building/schemas-and-sdks#cli-tools) を使うか、AgenticAdvertising.org のアシスタント [Addie](https://agenticadvertising.org) とチャットして AdCP タスクをテストできます。

## ツールコールパターン

### 基本のツール呼び出し

```javascript theme={null}
// Standard MCP tool call
const response = await mcp.call('get_products', {
  brand: {
    domain: "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
```

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

```javascript theme={null}
// Structured parameters
const response = await mcp.call('get_products', {
  brand: {
    domain: "betnow.com"
  },
  brief: "Sports betting app for March Madness",
  filters: {
    channels: ["ctv"],
    delivery_type: "guaranteed",
    max_cpm: 50
  }
});
```

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

```javascript theme={null}
// 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 MCP レスポンスは**フラット構造**を使用します — エンベロープフィールド（`status`、`context_id`、`context`、`task_id`、`timestamp`、`replayed`、`adcp_error`、`governance_context`）とタスクボディフィールドが、ツールレスポンスのルートに兄弟として現れます。[`core/protocol-envelope.json`](https://adcontextprotocol.org/schemas/v3/core/protocol-envelope.json) で定義される `payload` オブジェクトは文書上のグルーピング構造であり、シリアライズされるワイヤーキーでは**ありません**: ボディフィールドは MCP 上で `payload:` キーの下にネストされ**ません**。これは MCP のネイティブな `structuredContent` の慣習に一致します。

```json theme={null}
{
  "status": "completed",                  // envelope: unified task status
  "message": "Found 5 products",          // envelope: human-readable summary
  "context_id": "ctx-abc123",             // envelope: session identifier (server-managed)
  "context": { "ui": "buyer_dashboard" }, // envelope: per-request opaque echo (caller-owned)
  "timestamp": "2026-05-19T14:25:30Z",    // envelope: response generation time
  "products": [...],                      // body: task-specific data, sibling of envelope fields
  "errors": [...]                         // body: per-record / payload-level errors (warning severity allowed)
}
```

**プロデューサールール。** MCP ツール実装は、エンベロープフィールドとボディフィールドをルートにフラットな兄弟として発行しなければなりません（MUST）。ボディフィールドを `payload:` キーの下にネストするのは非コンフォーマントです — レシーバーはフラットなルートから解析し、ネストされた表現はすべての出荷済み SDK を壊します。

**レシーバールール。** MCP ツールコンシューマーは、ツールレスポンスのフラットなルートからエンベロープとボディのフィールドを解析しなければなりません（MUST）。レシーバーはネストされた `payload:` キーを要求してはなりません（MUST NOT）。スキーマの `payload` はドキュメントであり、ワイヤー要件ではありません。レスポンスに `status` が不在の場合（レガシーまたはトランスポートネイティブの状態キャリア）、レシーバーは非エラーレスポンスについて `completed` をデフォルトとし、エラーエンベロープについては `adcp_error` を検査しなければなりません（MUST）。

**`context_id` vs `context` — 意味的に直交。**

* `context_id` は、複数のツール呼び出しにわたって関連オペレーションを追跡するための**サーバー管理のセッション識別子**です。サーバーがそれを発行し、呼び出し元はセッションをつなぐため後続の呼び出しでエコーしてもよい（MAY）。MCP のトランスポートレベルセッションとは別物です。
* `context` は、**呼び出し元が供給する不透明なエコーオブジェクト**（[`core/context.json`](https://adcontextprotocol.org/schemas/v3/core/context.json)）です — エージェントは解析せずにバイト単位で保持します。バイヤー側の相関（UI セッション ID、トレース ID、カスタムメタデータ）に使われます。
* 両方が同じレスポンスに現れてもよい（MAY）。これらはエイリアスでは**ありません**。

**ステータス処理**: 完全なステータス処理パターンは [Task Lifecycle](/docs/building/by-layer/L3/task-lifecycle) を参照してください。

## 利用可能なツール

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

### プロトコルツール

```javascript theme={null}
await mcp.call('get_adcp_capabilities', {...});  // Discover agent capabilities (start here)
```

### Media Buy ツール

```javascript theme={null}
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
```

### Signals ツール

```javascript theme={null}
await mcp.call('get_signals', {...});      // Discover audience signals
await mcp.call('activate_signal', {...});  // Deploy signals to platforms
```

**タスクパラメータ**: [Media Buy](/docs/media-buy) および [Signals](/docs/signals/overview) セクションの各タスクドキュメントを参照。

## トランスポートラッパーとしての MCP Tasks

AdCP のタスクライフサイクル状態はアプリケーション層の状態です。MCP Tasks は `tools/call` リクエストをラップして、LLM ではなく MCP クライアントが `CallToolResult` を待てるようにします。これらは AdCP の `task_id`、ステータスペイロード、Webhook、ポーリング/リコンシリエーション面を置き換えません。

タスク拡張された MCP 呼び出しは、`status` がまだ `submitted` である AdCP ペイロードを配信した後に正常に完了できます。その時点から、メディアバイ、クリエイティブ、シグナル、またはガバナンスのワークフローは AdCP 層で開いたままであり、Webhook または AdCP ポーリングで観測すべきです。

:::warning クライアントサポートは限定的
ほとんどのチャットベース MCP クライアント（Claude Desktop、Cursor）はまだ MCP Tasks をサポートしていません。クライアントがタスク拡張ツール呼び出しをサポートしない場合、代わりに標準の `tools/call` に **Webhook** または **AdCP ポーリング**を加えて使用してください — これらは任意の MCP クライアントで動作します。トランスポート非依存のパターンは [Async Operations](/docs/building/by-layer/L3/async-operations) と [Push Notifications](/docs/building/by-layer/L3/webhooks) を参照してください。

MCP Tasks は、MCP クライアントを自分で制御する場合（例: `@modelcontextprotocol/sdk` で独自のオーケストレーターを構築）に、初回の `tools/call` 結果のプロトコルレベルの待機が欲しいときに有用です。これらは任意のトランスポート配管であり、正準の AdCP タスクストアではありません。
:::

### SDK 実装

`@modelcontextprotocol/sdk` パッケージを使う場合、MCP Tasks のサポートは最小限のコードで済みます。`InMemoryTaskStore`（または独自の `TaskStore` 実装）を Server コンストラクターに渡します — SDK が `tasks/get`、`tasks/result`、`tasks/list`、`tasks/cancel` のハンドラーを自動登録します:

```typescript theme={null}
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { InMemoryTaskStore } from '@modelcontextprotocol/sdk/experimental/tasks';

const taskStore = new InMemoryTaskStore();

const server = new Server(
  { name: 'my-adcp-agent', version: '1.0.0' },
  {
    capabilities: {
      tools: {},
      tasks: {
        list: {},
        cancel: {},
        requests: { tools: { call: {} } },
      },
    },
    taskStore,
  },
);
```

`tools/call` ハンドラーで、`task` フィールドを確認してストアを使います:

```typescript theme={null}
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
  const taskField = request.params.task;
  const result = await executeMyTool(request.params);

  if (!taskField) return result; // Synchronous path

  // Task-augmented: extra.taskStore handles requestId, sessionId,
  // and sends notifications/tasks/status on completion
  const task = await extra.taskStore.createTask({ ttl: taskField.ttl });
  await extra.taskStore.storeTaskResult(
    task.taskId,
    result.isError ? 'failed' : 'completed',
    result,
  );
  return { task: await extra.taskStore.getTask(task.taskId) };
});
```

SDK はポーリング、キャンセル、TTL クリーンアップ、`tasks/result` レスポンスの `_meta` 注入を処理します。`InMemoryTaskStore` は非永続です — 本番では、データベースでバックアップされた `TaskStore` を実装してください。

`Server` の代わりに `McpServer` を使う場合、`server.experimental.tasks.registerToolTask()` でタスク対応ツールを登録します — 高レベル API は `taskSupport` を宣言するツールについてこれを強制します。

:::warning 本番のタスク分離
`InMemoryTaskStore` はタスクをセッションでスコープしません — タスク ID を知る任意のクライアントがそれを読み取り、キャンセル、リストできます。本番では、すべてのオペレーションで `sessionId` によりフィルタリングする `TaskStore` を実装してください。また、クライアント提供の TTL 値をサーバー側でクランプし、タスク作成にレート制限を強制してください。
:::

### サーバーケイパビリティ

AdCP MCP サーバーはケイパビリティで `tasks` を宣言します:

```json theme={null}
{
  "capabilities": {
    "tools": {},
    "tasks": {
      "list": {},
      "cancel": {},
      "requests": {
        "tools": { "call": {} }
      }
    }
  }
}
```

### ツールレベルのタスクサポート

各ツールは、`execution.taskSupport` を通じてタスク拡張実行をサポートするかどうかを宣言します:

| ツール                      | `taskSupport` | 根拠                        |
| ------------------------ | ------------- | ------------------------- |
| `get_products`           | `optional`    | 複雑な検索、HITL の明確化           |
| `create_media_buy`       | `optional`    | 外部システム、承認ワークフロー           |
| `update_media_buy`       | `optional`    | 外部システム更新                  |
| `build_creative`         | `optional`    | 人間のクリエイティブレビュー、長時間の制作レンダー |
| `sync_creatives`         | `optional`    | アセット処理とトランスコード            |
| `get_signals`            | `optional`    | 複雑なオーディエンス発見              |
| `activate_signal`        | `optional`    | プラットフォームデプロイ              |
| `sync_plans`             | `optional`    | ガバナンスプラン処理                |
| `check_governance`       | `optional`    | 外部ポリシー評価                  |
| `report_plan_outcome`    | `optional`    | 外部システム更新                  |
| `acquire_rights`         | `optional`    | 承認ワークフロー                  |
| `update_rights`          | `optional`    | 外部更新                      |
| `get_rights`             | `optional`    | 外部ルックアップ                  |
| `get_adcp_capabilities`  | `forbidden`   | 即時、静的                     |
| `list_creative_formats`  | `forbidden`   | 即時カタログルックアップ              |
| `preview_creative`       | `forbidden`   | 既存マニフェストをレンダー             |
| `list_creatives`         | `forbidden`   | セッション状態ルックアップ             |
| `get_media_buys`         | `forbidden`   | セッション状態ルックアップ             |
| `get_media_buy_delivery` | `forbidden`   | セッション状態ルックアップ             |
| `get_creative_delivery`  | `forbidden`   | セッション状態ルックアップ             |
| `get_plan_audit_logs`    | `forbidden`   | セッション状態ルックアップ             |
| `get_brand_identity`     | `forbidden`   | 即時ルックアップ                  |

`taskSupport: "optional"` のツールはどちらの方法でも呼び出せます:

* **`task` フィールドなし**: 同期 — 結果を直接返す
* **`task` フィールドあり**: 即座に `CreateTaskResult` を返す。トランスポートネイティブの `tasks/get` で MCP タスクをポーリングし、トランスポートネイティブの `tasks/result` で `CallToolResult` を取得し、その結果内の AdCP ペイロードを検査する。

### ツールをタスクとして呼び出す

`tools/call` リクエストに `task` フィールドを含めます:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_products",
    "arguments": {
      "buying_mode": "brief",
      "brief": "Premium CTV inventory for luxury auto"
    },
    "task": {
      "ttl": 3600000
    }
  }
}
```

サーバーは即座にタスクハンドルを返します:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "task": {
      "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
      "status": "working",
      "statusMessage": "Searching inventory for luxury auto CTV placements",
      "createdAt": "2025-11-25T10:30:00Z",
      "lastUpdatedAt": "2025-11-25T10:30:00Z",
      "ttl": 3600000,
      "pollInterval": 5000
    }
  }
}
```

クライアントは、タスクが終端状態（`completed`、`failed`、`cancelled`）に達するまで `tasks/get` で MCP トランスポートタスクをポーリングし（`pollInterval` を尊重）、その後 `tasks/result` で `CallToolResult` を取得します。トランスポートタスクを中止するには、MCP `taskId` を付けて `tasks/cancel` を送ります。

`CallToolResult` を取得した後、AdCP レスポンスペイロードを検査します。それが `status: "submitted"` と AdCP `task_id` を含む場合、トランスポートタスクはキューイングされた AdCP レスポンスを配信しましたが、アプリケーションワークフローはまだ開いています。Webhook または AdCP ポーリング（`get_task_status`、または 3.x のレガシー `tasks/get`）で続行します。

### MCP タスクステータス vs. AdCP ステータス

AdCP は MCP Tasks より豊富なステータスセットを使用します。実装が AdCP の進捗をトランスポートネイティブの MCP タスクにミラーする場合、このマッピングは MCP ラッパーにのみ使用してください。AdCP ペイロードがドメインワークフロー状態の真実の源のままです:

| AdCP ステータス       | MCP タスクステータス     | 備考                                                               |
| ---------------- | ---------------- | ---------------------------------------------------------------- |
| `working`        | `working`        | 直接マッピング                                                          |
| `submitted`      | `working`        | キュー状態を示すため `statusMessage` を使う                                   |
| `input-required` | `input_required` | サーバーがタスクを `input_required` に移動し、`tasks/result` で elicitation を送る |
| `completed`      | `completed`      | 直接マッピング                                                          |
| `failed`         | `failed`         | 直接マッピング                                                          |
| `rejected`       | `failed`         | 拒否理由には `statusMessage` を使う                                       |
| `canceled`       | `cancelled`      | スペルの違い（AdCP は米式、MCP は英式）                                         |
| `auth-required`  | `input_required` | Elicitation がクレデンシャルを要求                                          |

### 長寿命オペレーションのための Webhook

MCP Tasks は MCP セッション内での待機を処理しますが、多くの AdCP オペレーションは単一のセッションより長く続きます（例: パブリッシャー承認に 24 時間かかるメディアバイ）。これらについては、AdCP 呼び出しに `push_notification_config` を登録します:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_media_buy",
    "arguments": {
      "buyer_ref": "nike_q1_2025",
      "packages": [],
      "push_notification_config": {
        "url": "https://buyer.com/webhooks/adcp/create_media_buy/op_abc123",
        "authentication": {
          "schemes": ["HMAC-SHA256"],
          "credentials": "shared_secret_32_chars"
        }
      }
    },
    "task": {
      "ttl": 86400000
    }
  }
}
```

MCP タスクはセッション内のトランスポートラッパーを追跡します。Webhook は AdCP アプリケーションタスクを独立して追跡し、MCP セッション終了後も有効なままです。Webhook のペイロード形式と認証は [Push Notifications](/docs/building/by-layer/L3/webhooks) を参照してください。

## コンテキスト管理（MCP 固有）

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

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

```javascript theme={null}
class McpAdcpSession {
  constructor(mcpClient) {
    this.mcp = mcpClient;
    this.contextId = null;
  }

  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;
    }

    // Optionally augment with an MCP Task wrapper
    if (options.task) {
      request.task = options.task;
    }

    const response = await this.mcp.callTool(request);

    // Save context for next call
    if (response.context_id) {
      this.contextId = response.context_id;
    }

    return response;
  }

  reset() {
    this.contextId = null;
  }
}
```

### 使用例

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

```javascript theme={null}
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
```

#### MCP Tasks を用いた非同期処理

`taskSupport: "optional"` のツールでは、`task` オプションを渡して MCP Tasks を使います:

```javascript theme={null}
const session = new McpAdcpSession(mcp);

// Synchronous call (no task augmentation)
const products = await session.call('get_products', {
  buying_mode: 'brief',
  brief: "Sports campaign"
});

// Task-augmented call for a long-running operation
const result = await session.call('create_media_buy',
  {
    packages: [...],
  },
  {
    task: { ttl: 86400000 },  // 24-hour TTL
    push_notification_config: {  // Webhook backup for session-outliving ops
      url: "https://buyer.com/webhooks/adcp/create_media_buy/op_abc123",
      authentication: {
        schemes: ["HMAC-SHA256"],
        credentials: "shared_secret_32_chars"
      }
    }
  }
);

// result is a CreateTaskResult for the MCP wrapper.
// After tasks/result, inspect the AdCP payload; if it is still submitted,
// continue via webhook or AdCP get_task_status / legacy tasks/get.
```

**Webhook POST format:**

```json theme={null}
{
  "task_id": "task_456",
  "status": "completed",
  "timestamp": "2025-01-22T10:30:00Z",
  "result": {
    "media_buy_id": "mb_12345",
    "packages": [...]
  }
}
```

**Note:** レシーバーは、Webhook URL を解析するのではなく、ペイロードボディの `operation_id`（および `task_type`）を使って Webhook を相関しなければなりません（MUST）。バイヤーは自身のサーバー側ルーティングの便宜のため `operation_id` を URL パスやクエリに埋め込んでもよい（MAY、URL 構造はセラーにとって不透明で完全にバイヤー定義）が、セラーはその URL を決して解析しません — セラーは登録時に渡されたバイヤー供給の `operation_id` をエコーし、相関のワイヤーレベルの真実の源はペイロードフィールドです。[`mcp-webhook-payload.json`](https://adcontextprotocol.org/schemas/v3/core/mcp-webhook-payload.json) と [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates) を参照してください。

`result` フィールドには AdCP のデータペイロードが入ります。`completed`/`failed` ではタスクレスポンス全体（例: `create-media-buy-response.json`）、それ以外のステータスではステータス別スキーマ（例: `create-media-buy-async-response-working.json`）を使用します。

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

[`mcp-webhook-payload.json`](https://adcontextprotocol.org/schemas/v3/core/mcp-webhook-payload.json) には以下が含まれます:

**必須フィールド:**

* `idempotency_key` — 発火ごとのトランスポート重複排除キー（完全なセマンティクスはスキーマを参照）
* `operation_id` — バイヤー供給の相関識別子で、セラーがそのままエコーする。レシーバーは URL パスでは**なく**これを使って通知を発信元タスクにルーティングする。セラーは URL を解析してこれを導出してはならない（MUST NOT）。URL 構造はセラーの視点からは実装依存である。
* `task_id` — 相関用の一意なタスク ID
* `task_type` — タスクごとのハンドラーにルーティングするためのタスク名（例: `create_media_buy`, `sync_creatives`）
* `status` — 現在のタスクステータス（completed, failed, working, input-required など）
* `timestamp` — Webhook 生成時の ISO 8601 タイムスタンプ

**任意フィールド:**

* `notification_id` — 再発行追跡のためのイベント層の安定 ID（スキーマを参照）
* `protocol` — AdCP プロトコルファミリー（`media-buy` または `signals`）
* `context_id` — 会話/セッション ID
* `message` — ステータス変更に関する人間向けコンテキスト

**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` フィールドはステータス別スキーマを使用します:

| Status           | Schema                                      | Contents                   |
| ---------------- | ------------------------------------------- | -------------------------- |
| `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`](https://adcontextprotocol.org/schemas/v3/core/async-response-data.json)

#### Webhook Handler Example

```javascript theme={null}
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,
          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}`;
}
```

#### タスク管理とポーリング

```javascript theme={null}
// Check status of a specific AdCP task
const taskStatus = await session.call('get_task_status', {
  task_id: 'task_456',
  include_result: true
});
if (taskStatus.status === 'completed') {
  console.log('Result:', taskStatus.result);
}

// State reconciliation
const reconciliation = await session.call('list_tasks', {
  filters: { statuses: ['submitted', 'working', 'input-required'] }
});
if (reconciliation.tasks.length > 0) {
  console.log('Found open AdCP tasks:', reconciliation.tasks);
  // Start tracking these tasks
}
```

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

```javascript theme={null}
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` を明示的に扱う必要があります。

## 非同期処理の扱い

AdCP レスポンスが `working` または `submitted` を返す場合、結果を受け取る方法が必要です。これは MCP クライアントが MCP Tasks をサポートするかどうかに関わらず適用されます — 以下のパターンは任意のクライアントで動作します。

| アプローチ         | 最適な用途                 | トレードオフ                                                   |
| ------------- | --------------------- | -------------------------------------------------------- |
| **Webhooks**  | 本番システム、任意のタスク時間       | 数時間/数日を扱えるが、公開エンドポイントが必要                                 |
| **Polling**   | シンプルな統合、短時間タスク        | 実装が簡単だが、長時間待機に非効率                                        |
| **MCP Tasks** | MCP SDK を使うカスタムクライアント | 初回 `tools/call` のためのプロトコルネイティブなラッパーだが、AdCP のタスク追跡を置き換えない |

### オプション 1: Webhook（推奨）

Webhook URL を設定すると、オペレーション完了時にサーバーが結果を POST します。これは外部依存（パブリッシャー承認、人間のレビュー）でブロックされる `submitted` オペレーションに適したアプローチです。

```javascript theme={null}
const response = await session.call('create_media_buy',
  {
    packages: [...],
    budget: { total: 150000, currency: "USD" }
  },
  {
    push_notification_config: {
      url: "https://buyer.com/webhooks/adcp/create_media_buy/op_abc123",
      authentication: {
        schemes: ["HMAC-SHA256"],
        credentials: "shared_secret_32_chars"
      }
    }
  }
);

// If status is 'submitted', the server will POST the result to your webhook
// No polling needed — just handle the webhook when it arrives
```

ペイロード形式と認証は [Push Notifications](/docs/building/by-layer/L3/webhooks) を参照してください。

### オプション 2: ポーリング（バックアップ）

`submitted` オペレーションのバックアップとして、または Webhook エンドポイントを公開できない場合に AdCP ポーリングを使います。3.x では、セラーが宣伝する場合は `get_task_status` を優先し、そうでなければレガシー `tasks/get` を使います:

```javascript theme={null}
async function pollForResult(session, taskId, pollInterval = 30000) {
  while (true) {
    const response = await session.call('get_task_status', {
      task_id: taskId,
      include_result: 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
      });
    }

    await new Promise(resolve => setTimeout(resolve, pollInterval));
  }
}
```

### ステータス別の扱い

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

switch (initial.status) {
  case 'completed':
    // Done — result is inline
    console.log('Created:', initial.media_buy_id);
    break;

  case 'working':
    // Server is actively processing (>30s) — just wait, result will arrive
    // No polling needed; 'working' is a progress signal, not a polling trigger
    console.log('Processing:', initial.message);
    break;

  case 'submitted':
    // Blocked on external dependency — use webhook or poll
    console.log(`Task ${initial.task_id} queued for approval`);
    break;

  case 'input-required':
    // Blocked on user input
    console.log('Need more info:', initial.message);
    break;
}
```

## 統合の例

```javascript theme={null}
// コンテキスト管理付きで 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':
      // Server is actively processing — just wait, result will arrive
      console.log('Processing:', response.message);
      return response;

    case 'submitted':
      // Blocked on external dependency — webhook or poll
      console.log(`Task ${response.task_id} submitted, webhook will notify`);
      return { pending: true, task_id: response.task_id };

    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 固有の考慮点

### サーバー側のツールラッパーはエンベロープフィールドを許容しなければならない

バイヤー SDK は、エンベロープレベルのフィールド（`idempotency_key`、`context_id`、`context`、`governance_context`、`push_notification_config`）を、それらを消費しない読み取り専用ツールを含め、すべての AdCP ツール呼び出しで一様に送信します。MCP ツール実装はこれらのフィールドを受け入れ、使わないものを無視しなければなりません（MUST）。エンベロープフィールドが存在するという理由で呼び出しを拒否してはなりません（MUST NOT）。よくある罠:

* **FastMCP / Pydantic の厳格なシグネチャ** — `idempotency_key: str | None = None`（および他のエンベロープフィールド）を受け入れて無視するオプショナルとして宣言するか、`**kwargs` で未知のものを飲み込みます。入力モデルを制御できる場合は `model_config = ConfigDict(extra='allow')`。
* **Zod / valibot の入力スキーマの `.strict()`** — `.strict()` を外すか、passthrough バリアントを使います。
* **入力モデルに `additionalProperties: false` を注入する OpenAPI codegen** — ジェネレーター設定を修正します。スペックのリクエストスキーマは `additionalProperties: true` を宣言しています。

`idempotency_key` に対して `unexpected_keyword_argument` を送出するラッパーは、エンベロープ契約に従う任意のバイヤー SDK に対してコンプライアンスに失敗します。規範ルールは [security.mdx > Server-side tool wrapper conformance](/docs/building/by-layer/L1/security#server-side-tool-wrapper-conformance) を参照してください。

### ツールディスカバリー

```javascript theme={null}
// List available tools — use get_adcp_capabilities for runtime feature detection
const tools = await mcp.listTools();

// Check which tools support async execution
const asyncTools = tools.filter(t => t.execution?.taskSupport === 'optional');
```

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

<Note>
  **推奨**: 実行時の機能発見には [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities) を使用してください。サーバーカード拡張はツールカタログやレジストリ向けの静的メタデータを提供します。
</Note>

MCP サーバーは `/.well-known/mcp.json`（または `/.well-known/server.json`）のサーバーカードで AdCP 対応を宣言できます。AdCP 固有メタデータは `adcontextprotocol.org` 名前空間の `_meta` フィールドに記載します。

```json theme={null}
{
  "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 対応の検出:**

```javascript theme={null}
// 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](/docs/building/by-layer/L2/context-sessions#extension-fields-ext) 参照）
* バージョンに基づく互換性チェックが可能

**Note:** `_meta` フィールドは [MCP server.json spec](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md) に従い逆 DNS の名前空間を使用します。`/.well-known/mcp.json` と `/.well-known/server.json` の両方をサポートしてください。

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

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

### エラーハンドリング

AdCP エラーは `isError: true` のツールレベルレスポンスとして `structuredContent.adcp_error` にエラーが格納されて返されます。完全な抽出ロジックと JSON-RPC トランスポートコードは [Transport Error Mapping](/docs/building/operating/transport-errors) を参照してください。

```javascript theme={null}
try {
  const response = await session.call('get_products', params);

  // AdCP アプリケーションエラーを確認（isError: true かつ構造化データあり）
  if (response.isError) {
    const adcpError = response.structuredContent?.adcp_error;
    if (adcpError) {
      // code, recovery, retry_after などを含む構造化エラー
      console.log('AdCP error:', adcpError.code, adcpError.recovery);
    }
  }
} catch (mcpError) {
  // MCP トランスポートエラー（接続、認証など）
  // AdCP 構造化トランスポートエラーを確認
  const adcpError = mcpError.data?.adcp_error;
  if (adcpError) {
    console.log('Transport error:', adcpError.code);
  } else {
    console.error('MCP Error:', mcpError);
  }
}
```

## ベストプラクティス

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

## 次のステップ

* **Core Concepts**: ステータス処理とワークフローは [Task Lifecycle](/docs/building/by-layer/L3/task-lifecycle) を参照
* **Task Reference**: [Media Buy Tasks](/docs/media-buy) と [Signals](/docs/signals/overview)
* **Protocol Comparison**: [A2A integration](/docs/building/by-layer/L0/a2a-guide) と比較
* **Examples**: 完全なワークフロー例は Core Concepts に掲載

**ステータス処理、非同期オペレーション、確認フローについては [Task Lifecycle](/docs/building/by-layer/L3/task-lifecycle) を参照してください。このガイドは MCP トランスポート固有の内容に絞っています。**
