К основному содержанию
плагин

adcp-client — плагин для Claude Code: вызов инструментов AdCP

Плагин добавляет скилл /adcp для работы с рекламными агентами AdCP через MCP или A2A. Он пригодится для проверки совместимости агента с протоколом и поиска в реестре AdCP.

Установить в Claude Code · Через маркетплейс плагинов Все способы и платформы ↓
/plugin marketplace add anthropics/claude-plugins-community
/plugin install adcp-client@claude-community
Открыть на GitHub

Что делает adcp-client

Скилл /adcp позволяет вызывать инструменты рекламных агентов AdCP через MCP или A2A. С его помощью также можно запускать сценарии проверки соответствия протоколу и запрашивать реестр AdCP. Это плагин для задач, где агентам нужно взаимодействовать по AdCP.

В комплекте есть тестовые агенты, поэтому для пробного запуска не требуется отдельная настройка агентов. Плагин рассчитан на Claude Code; для работы с агентами использует MCP или A2A. Если ваша интеграция работает только с другим протоколом, этот способ вызова не подойдёт.

Какие задачи решает

  • вызвать инструмент рекламного агента AdCP
  • проверить агента сценариями соответствия протоколу
  • найти сведения об агенте в реестре AdCP
  • попробовать работу через встроенного тестового агента

Примеры запросов агенту

  • Вызови инструмент рекламного агента AdCP через MCP.
  • Запусти сценарии проверки соответствия агента протоколу AdCP.
  • Найди нужного агента в реестре AdCP.

Как установить adcp-client

Через маркетплейс плагинов

/plugin marketplace add anthropics/claude-plugins-community
/plugin install adcp-client@claude-community

По шагам, с командами для каждой платформы: Плагины Claude Code: маркетплейсы, установка и как собрать свой

Вопросы о adcp-client

Как установить adcp-client в Claude Code?

Исправленные команды: /plugin marketplace add anthropics/claude-plugins-community и /plugin install adcp-client@claude-community.

Что делает adcp-client?

Добавляет скилл /adcp для вызова инструментов рекламных агентов через MCP или A2A. Также скилл запускает сценарии проверки протокола и обращается к реестру AdCP.

Нужны ли отдельные агенты для пробного запуска?

Нет, в плагин включены тестовые агенты для запуска без настройки агентов.

Исходный текст

SKILL.md · Apache-2.0
# @adcp/sdk

[![npm version](https://badge.fury.io/js/@adcp%2Fsdk.svg)](https://badge.fury.io/js/@adcp%2Fsdk)
[![npm downloads](https://img.shields.io/npm/dm/@adcp/sdk.svg)](https://www.npmjs.com/package/@adcp/sdk)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![API Documentation](https://img.shields.io/badge/API-Documentation-blue.svg)](https://adcontextprotocol.github.io/adcp-client/api/)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/adcontextprotocol/adcp-client/ci.yml?branch=main)](https://github.com/adcontextprotocol/adcp-client/actions)

Official TypeScript/JavaScript SDK for the **Ad Context Protocol (AdCP)**. Use its buyer client to call agents, server framework to build them, and conformance tools to test them. The SDK supports MCP and A2A, with both immediate and asynchronous task results.

## For AI Agents

Start with [`docs/llms.txt`](./docs/llms.txt) — the full protocol spec in one file (tools, types, error codes, examples). Building a server? See [`docs/guides/BUILD-AN-AGENT.md`](./docs/guides/BUILD-AN-AGENT.md). **Calling** an AdCP agent as a buyer? Load [`skills/call-adcp-agent/SKILL.md`](./skills/call-adcp-agent/SKILL.md) — wire contract, async flow, and error-recovery priors that aren't in the type signatures. Setting up request signing? See [`docs/guides/SIGNING-GUIDE.md`](./docs/guides/SIGNING-GUIDE.md). For type signatures, use [`docs/TYPE-SUMMARY.md`](./docs/TYPE-SUMMARY.md). Skip `src/lib/types/*.generated.ts` — they're machine-generated and will burn context.

These docs are also available in `node_modules/@adcp/sdk/docs/` after install.

## The Core Concept

AdCP operations are **distributed and asynchronous by default**. An agent might:

- Complete your request **immediately** (synchronous)
- Need time to process and **send results via webhook** (asynchronous)
- Ask for **clarifications** before proceeding
- Send periodic **status updates** as work progresses

**Your code stays the same.** You write handlers once, and they work for both sync completions and webhook deliveries.

## Installation

```bash
npm install @adcp/sdk              # 16.x, AdCP 3.2
npm install @adcp/[email protected]     # 13.x, maintained AdCP 3.1 line
npm install @adcp/[email protected]     # 7.11.x, AdCP 3.0
```

AdCP 2 reached end of life on August 1, 2026. This checkout supports AdCP 3 agents only; review the [AdCP 2 retirement guide](./docs/migration-drop-adcp-2.md) before upgrading an integration that still calls AdCP 2 sellers.

Upgrading to SDK 16? Read the [SDK 16 release notes](./docs/releases/16.0.0.md) and [release-bound upgrade worksheet](./docs/migration-14.x-rc-worksheet.md). The npm `latest` tag tracks SDK 16 (AdCP 3.2); SDK 13 stays available on `adcp-3.1` as the maintained AdCP 3.1 line. SDK 16 requires Node.js `^20.19.0 || >=22.12.0`. Earlier migration guides: [SDK 15 OAuth](./docs/migration-oauth-issuer-bindings.md), [13-to-14](./docs/migration-13-to-14.md), [12-to-14](./docs/migration-12-to-14.md), [12-to-13](./docs/migration-12-to-13.md), **[MIGRATION-v8.md](./MIGRATION-v8.md)**, and [8.0-to-8.1](./docs/migration-8.0-to-8.1.md).

### Narrow type imports (`@adcp/sdk/types/<tool>`)

Adopters who only need a single AdCP tool's types can import a per-tool slice instead of the full surface. Each slice is a self-contained `.d.ts` covering the tool's `Request` / `Response` / `Success` / `Error` / `Submitted` types and every type they reference:

```ts
import type { SyncAccountsRequest } from '@adcp/sdk/types/sync-accounts';
```

Why bother: the full `@adcp/sdk` type surface is ~45,000 lines and crashes `tsc` at Node's default 4 GB heap under `strict + skipLibCheck:false`. A single per-tool slice peaks at ~50 MB. That's the difference between adopters chasing the cryptic `FATAL: mark-compact` Node flag and just having their build pass.

Slices use kebab-case filenames matching the schema cache (`sync_accounts` → `@adcp/sdk/types/sync-accounts`). Requires `moduleResolution: "node16"` / `"nodenext"` / `"bundler"` on the adopter side. A machine-readable index of available slices ships at `@adcp/sdk/types/per-tool-index.json`.

### TypeScript footprint in monorepos

Large workspaces should keep the generated schema surface out of the default type-check path unless they actually need runtime Zod validators. The root `@adcp/sdk` export and `@adcp/sdk/types` do not re-export generated Zod schemas; import those schemas from `@adcp/sdk/schemas` so ordinary SDK imports avoid pulling the full generated schema declaration set into `tsc`.

| Need                                          | Recommended import                                                                      |
| --------------------------------------------- | --------------------------------------------------------------------------------------- |
| Client, server, signing, and response helpers | `@adcp/sdk`, `@adcp/sdk/client`, `@adcp/sdk/server`, or another focused runtime subpath |
| One tool's request/response types             | `@adcp/sdk/types/<tool>` such as `@adcp/sdk/types/sync-accounts`                        |
| Runtime Zod schemas and tool schema maps      | `@adcp/sdk/schemas`                                                                     |
| Runtime Zod schemas in browser UIs            | `@adcp/sdk/schemas/browser`                                                             |
| Broad generated protocol type barrel          | `@adcp/sdk/types`                                                                       |

Browser widgets and dashboards can validate AdCP objects with the generated Zod schemas:

```ts
import {
  EventSourceHealthSchema,
  SyncAudiencesResponseSchema,
  SyncEventSourcesResponseSchema,
} from '@adcp/sdk/schemas/browser';

const health = EventSourceHealthSchema.parse(rawHealth);
```

The browser entry also exports `GetProductsRequestSchema` and `LegacyGetProductsRequestSchema`, `BiddingPolicySchema`, `CanonicalBudgetAllocationSchema`, and the strict `SyncCreatives*` validators, with the same validation behavior as the Node entry. It has no Node built-ins in its import graph. JSON Schema loading and tool registration helpers remain available from `@adcp/sdk/schemas`. The generated schemas still initialize together, so importing one schema can retain the full generated module in a browser bundle; per-schema tree-shaking is tracked in [#3178](https://github.com/adcontextprotocol/adcp-client/issues/3178).

### Focused client runtime (`@adcp/sdk/client/core`)

Applications that only call AdCP agents can avoid evaluating the SDK's server,
compliance, testing, and unrelated protocol surfaces:

```ts
import { ADCPMultiAgentClient, InMemoryWebhookRegistrationStore } from '@adcp/sdk/client/core';
```

The existing `@adcp/sdk/client` path remains an alias of the package root for
compatibility. Use `/client/core` when cold import and test-worker startup time
matter.

For application monorepos, keep `skipLibCheck: true` unless you are intentionally auditing SDK declarations. If a package only needs request/response types for a few tools, prefer the per-tool slices over importing generated types through the root package or the broad `@adcp/sdk/types` barrel.

## Quick Start: AdCP 3.2

SDK 14 makes the compact 3.2 lifecycle primary:

```text
list_products → buy_products → control_media_buy
             ↘ request_proposals → refine_proposals → accept_proposal
```

Start with the persona guide that matches your job:

- [Build a seller](./docs/guides/SELLER-QUICKSTART-3.2.md)
- [Call a seller](./docs/guides/BUYER-QUICKSTART-3.2.md)
- [Adopt the account change feed](./docs/migration-account-change-feed.md)
- [Upgrade an integration that still uses AdCP 2](./docs/migration-drop-adcp-2.md)
- [Upgrade from SDK 13](./docs/migration-13-to-14.md)
- [Add AdCP to an existing platform](./docs/guides/EXISTING-PLATFORM.md)
- [Run in production](./docs/guides/PRODUCTION-DURABILITY.md)

```typescript
import { ADCPMultiAgentClient } from '@adcp/sdk';

const client = ADCPMultiAgentClient.simple('https://seller.example/mcp/', {
  authToken: process.env.ADCP_TOKEN,
});
const seller = client.agent('default-agent');

const listed = await seller.listProducts({
  account: { account_id: 'account-42' },
  brand: { domain: 'advertiser.example' },
});

if (listed.status === 'completed') {
  console.log(listed.data.products, listed.data.feed_version);
}
```

The established `get_products` / `create_media_buy` / `update_media_buy`
surface remains supported for AdCP 3.0/3.1 compatibility. Its detailed client
patterns follow below.

## Established lifecycle and distributed operations

```typescript
import { ADCPMultiAgentClient } from '@adcp/sdk';

// Configure agents and handlers
const client = new ADCPMultiAgentClient(
  [
    {
      id: 'agent_x',
      agent_uri: 'https://agent-x.com',
      protocol: 'a2a',
    },
    {
      id: 'agent_y',
      agent_uri: 'https://agent-y.com/mcp/',
      protocol: 'mcp',
    },
  ],
  {
    // Webhook URL template (macros: {agent_id}, {task_type}, {operation_id})
    webhookUrlTemplate: 'https://myapp.com/webhook/{task_type}/{agent_id}/{operation_id}',

    // Activity callback - fires for ALL events (requests, responses, status changes, webhooks)
    onActivity: activity => {
      console.log(`[${activity.type}] ${activity.task_type} - ${activity.operation_id}`);
      // Log to monitoring, update UI, etc.
    },

    // Status change handlers - called for ALL status changes (completed, failed, input-required, working, etc)
    handlers: {
      onGetProductsStatusChange: (response, metadata) => {
        // Called for sync completion, async webhook, AND status changes
        console.log(`[${metadata.status}] Got products for ${metadata.operation_id}`);

        if (metadata.status === 'completed') {
          db.saveProducts(metadata.operation_id, response.products);
        } else if (metadata.status === 'failed') {
          db.markFailed(metadata.operation_id, metadata.message);
        } else if (metadata.status === 'input-required') {
          // Handle clarification needed
          console.log('Needs input:', metadata.message);
        }
      },
    },
  }
);

// Execute operation - library handles operation IDs, webhook URLs, context management
const agent = client.agent('agent_x');
const result = await agent.getProducts({ brief: 'Coffee brands' });

// onActivity fired: protocol_request
// onActivity fired: protocol_response

// Check result
if (result.status === 'completed') {
  // Agent completed synchronously!
  console.log('✅ Sync completion:', result.data.products.length, 'products');
  // Products expose canonical format_options[]; legacy named-format refs do not
  // cross the primary AgentClient boundary.
  console.log(result.data.products[0]?.format_options[0]?.format_kind);
  // onGetProductsStatusChange handler ALREADY fired with status='completed' ✓
}

if (result.status === 'submitted') {
  // Agent will send webhook when complete
  console.log('⏳ Async - webhook registered at:', result.submitted?.webhookUrl);
  // onGetProductsStatusChange handler will fire when webhook arrives ✓
}
```

### Tracking a seller task after a restart

Persist `result.metadata.serverTaskId`, the original tool name, and
`result.metadata.taskId` (the buyer's operation ID) when a seller returns
`submitted` or `working`. A new client can reconstruct the polling handle:

```typescript
const tracked = await agent.trackTask({
  taskId: saved.serverTaskId,
  taskType: saved.taskType,
  operationId: saved.operationId,
});

const status = await tracked.track(); // One status check
const completed = await tracked.waitForCompletion(1000, AbortSignal.timeout(60_000));
```

Each poll uses AdCP `tasks_get` (MCP) or `tasks/get` (A2A) with
`{ task_id, include_result: true }`. Polls do not create separate
`protocol_request` / `protocol_response` activity records. Terminal
`status_change` activity carries the stable `operation_id` and the seller's
`server_task_id`; existing `task_id` fields retain their current meaning.
`TaskInfo` from the handle also carries `operationId` and `serverTaskId`.
The SDK checks the returned task ID and tool name before exposing a result.

Omitting `operationId` generates a UUID, available as `tracked.operationId`.
Reuse the handle within a client, or call `trackTask` again with the same
operation ID, seller task ID, and tool name to rebuild it. You can pin the ID before submitting work
with `TaskOptions.operationId`, for example
`agent.executeCustomTask('extension_task', params, undefined, { operationId })`.
Use a globally unique, opaque operation ID per dispatch (UUID recommended),
including across clients and tenants sharing persistence or webhook receivers.
Reuse it for tracking. To retry a dispatch after a crash, use a new operation ID
and the same `idempotency_key`. Operation IDs correlate observations;
`idempotency_key` provides retry safety. The SDK rejects local collisions;
uniqueness across processes is the caller's responsibility. Avoid sensitive
information in these IDs because webhook registration sends them to the seller.
They accept 1–255 letters, digits, underscores, periods, colons, or hyphens.
Dot-only IDs and the `delivery_route:` prefix are reserved.

Tracking returns the seller's original tool response shape and emits lifecycle
activity. It does not restore the original request's policy/projection context
or invoke inline tool completion handlers. Handle the returned result in the
caller. Pass `transport` or `signal` in the tracking request to configure the
polling lifetime; `track(transport)` overrides transport options for one poll.
Local cancellation, identity mismatches, missing tasks, and malformed poll
responses return or throw observation errors without publishing seller failure
events. Rebuild the handle with a fresh signal to resume after cancellation.
For A2A, stopping observation leaves seller work running by default. Set both
`cancelOnAbort: true` and `a2aTaskId` to request cancellation of seller work
when polling aborts; `a2aTaskId` must be the separate A2A transport task ID.

### Handling Clarifications (input-required)

When an agent needs more information, you can continue the conversation:

```typescript
const result = await agent.getProducts({ brief: 'Coffee brands' });

if (result.status === 'input-required') {
  console.log('❓ Agent needs clarification:', result.metadata.inputRequest?.question);
  // onActivity fired: status_change (input-required)

  // Continue the conversation with the same agent
  const refined = await agent.continueConversation('Only premium brands above $50');
  // onActivity fired: protocol_request
  // onActivity fired: protocol_response

  if (refined.status === 'completed') {
    console.log('✅ Got refined results:', refined.data.products.length);
    // onGetProductsStatusChange handler fired ✓
  }
}
```

## Webhook Pattern

All webhooks (task completions AND notifications) use one endpoint with flexible URL templates.

### Configure Your Webhook URL Structure

```typescript
const client = new ADCPMultiAgentClient(agents, {
  // Path-based (default pattern)
  webhookUrlTemplate: 'https://myapp.com/webhook/{task_type}/{agent_id}/{operation_id}',

  // OR query string
  webhookUrlTemplate: 'https://myapp.com/webhook?agent={agent_id}&op={operation_id}&type={task_type}',

  // OR custom path
  webhookUrlTemplate: 'https://myapp.com/api/v1/adcp/{agent_id}?operation={operation_id}',

  // OR namespace to avoid conflicts
  webhookUrlTemplate: 'https://myapp.com/adcp-webhooks/{agent_id}/{task_type}/{operation_id}',
});
```

### Single Webhook Endpoint

```typescript
// Handles ALL webhooks (task completions and notifications)
app.post('/webhook/:task_type/:agent_id/:operation_id', async (req, res) => {
  const { task_type, agent_id, operation_id } = req.params;

  // Route to agent client - handlers fire automatically
  const agent = client.agent(agent_id);
  await agent.handleWebhook(
    req.body,
    task_type,
    operation_id,
    req.headers['x-adcp-signature'],
    req.headers['x-adcp-timestamp']
  );

  res.json({ received: true });
});
```

### URL Generation is Automatic

```typescript
const operationId = createOperationId();
const webhookUrl = agent.getWebhookUrl('sync_creatives', operationId);
// Returns: https://myapp.com/webhook/sync_creatives/agent_x/op_123
// (or whatever your template generates)
```

## Activity Events

Get observability into everything happening:

```typescript
const client = new ADCPMultiAgentClient(agents, {
  onActivity: activity => {
    console.log({
      type: activity.type, // 'protocol_request', 'webhook_received', etc.
      operation_id: activity.operation_id,
      agent_id: activity.agent_id,
      status: activity.status,
    });

    // Stream to UI, save to database, send to monitoring
    eventStream.send(activity);
  },
});
```

Activity types:

- `protocol_request` - Request sent to agent
- `protocol_response` - Response received from agent
- `status_change` - Task status changed
- `webhook_received` - Webhook received from agent

## Notifications (Agent-Initiated)

**Mental Model**: Notifications are operations that get set up when you create a media buy. The agent sends periodic updates (like delivery reports) to the webhook URL you configured during media buy creation.

```typescript
// When creating a media buy, agent registers for delivery notifications
const result = await agent.createMediaBuy({
  campaign_id: 'camp_123',
  budget: { amount: 10000, currency: 'USD' },
  // Agent internally sets up recurring delivery_report notifications
});

// Later, agent sends notifications to your webhook
const client = new ADCPMultiAgentClient(agents, {
  handlers: {
    onMediaBuyDeliveryNotification: (notification, metadata) => {
      console.log(`Report #${metadata.sequence_number}: ${metadata.notification_type}`);

      // notification_type indicates progress:
      // 'scheduled' → Progress update (like status: 'working')
      // 'final' → Operation complete (like status: 'completed')
      // 'delayed' → Still waiting (extended timeline)

      db.saveDeliveryUpdate(metadata.operation_id, notification);

      if (metadata.notification_type === 'final') {
        db.markOperationComplete(metadata.operation_id);
      }
    },
  },
});
```

Notifications use the **same webhook URL pattern** as regular operations:

```
POST https://myapp.com/webhook/media_buy_delivery/agent_x/delivery_report_agent_x_2025-10
```

The `operation_id` is lazily generated from agent + month: `delivery_report_{agent_id}_{YYYY-MM}`

All intermediate reports for the same agent + month → same `operation_id`

## Type Safety

Full TypeScript support with IntelliSense:

```typescript
// All responses are fully typed
const result = await agent.getProducts(params);
// result: TaskResult<GetProductsResponse>

if (result.success) {
  result.data.products.forEach(p => {
    console.log(p.name, p.price); // Full autocomplete!
  });
}

// Handlers receive typed responses
handlers: {
  onCreateMediaBuyStatusChange: (response, metadata) => {
    // response: CreateMediaBuyResponse | CreateMediaBuyAsyncWorking | ...
    // metadata: WebhookMetadata
    if (metadata.status === 'completed') {
      const buyId = (response as CreateMediaBuyResponse).media_buy_id; // Typed!
    }
  };
}
```

### Platform Implementors

Building a server that receives AdCP tool calls? **v6 (recommended for new agents):** declare a typed `DecisioningPlatform` per-specialism and let the framework wire idempotency, signing, capability projection, async tasks, status normalization, and lifecycle state.

```typescript
import { serve } from '@adcp/sdk';
import {
  createAdcpServerFromPlatform,
  createIdempotencyStore,
  definePlatform,
  defineSalesCorePlatform,
  memoryBackend,
  refAccountId,
} from '@adcp/sdk/server';

// Single-process example. Use pgBackend(pool) or redisBackend(client) for
// durable, replica-safe production replay.
const idempotency = createIdempotencyStore({ backend: memoryBackend(), ttlSeconds: 86400 });

const platform = definePlatform({
  capabilities: {
    specialisms: ['sales-non-guaranteed'] as const,
    channels: ['display'] as const,
    pricingModels: ['cpm'] as const,
  },
  accounts: {
    resolve: async (ref, ctx) => {
      const id = refAccountId(ref);
      if (!id) return null; // → ACCOUNT_NOT_FOUND
      return db.findAccount(id, ctx);
    },
  },
  sales: defineSalesCorePlatform({
    getProducts: async (req, ctx) => ({ products: catalog.search(req) }), // req typed ✓
    createMediaBuy: async (req, ctx) => ({
      media_buy_id: 'mb_1',
      status: 'pending_creatives',
      confirmed_at: new Date().toISOString(),
      packages: [],
    }),
    updateMediaBuy: async (id, patch, ctx) => ({ media_buy_id: id, status: 'active' }),
    getMediaBuyDelivery: async (req, ctx) => ({
      currency: 'USD',
      reporting_period: { start: '2026-05-01T00:00:00Z', end: '2026-05-31T23:59:59Z' },
      media_buy_deliveries: [],
    }),
    getMediaBuys: async (req, ctx) => ({ media_buys: [] }),
  }),
});

serve(() => createAdcpServerFromPlatform(platform, { name: 'My Publisher', version: '1.0.0', idempotency }));
```

`RequiredPlatformsFor<S>` enforces specialism claims at compile time — claim `'sales-non-guaranteed'` and the typechecker requires `SalesCorePlatform & SalesIngestionPlatform` on `sales`. `creative-template` and `creative-generative` claims both map to `CreativeBuilderPlatform`; `creative-ad-server` is its own archetype with `listCreatives` + `getCreativeDelivery`.

**6.7 helpers worth knowing about:**

- `definePlatform` / `defineSalesCorePlatform` / `defineSalesIngestionPlatform` / sibling `define<X>Platform` factories — drop `req: unknown` casts on inline platform objects.
- `composeMethod(inner, { before, after })` — typed before/after wrappers around any platform method (caching, enrichment under `ext.*`, typed-error guards). Pre-built `accounts.resolve` guards: `requireAccountMatch`, `requireAdvertiserMatch`, `requireOrgScope`.
- Typed errors: `AuthMissingError`, `AuthInvalidError`, `PermissionDeniedError`, `RateLimitedError`, `ServiceUnavailableError`, `GovernanceDeniedError`, `IdempotencyConflictError`, plus the not-found family. `AuthRequiredError` remains as a deprecated `AUTH_REQUIRED` compatibility wrapper. Throw these instead of `new AdcpError(code, ...)`.
- `BuyerAgentRegistry` — durable buyer-agent identity surface threaded through `ctx.agent` to every `AccountStore` method. See [`docs/migration-buyer-agent-registry.md`](docs/migration-buyer-agent-registry.md).
- Three reference `AccountStore` shapes: `InMemoryImplicitAccountStore` (Shape A — buyer-driven `sync_accounts`), `createOAuthPassthroughResolver` (Shape B — vendor OAuth + `/me/adaccounts`), `createRosterAccountStore` (Shape C — publisher-curated roster).
- Multi-tenant: `createTenantRegistry({...})` for host-routed (one server per tenant) or `createTenantStore({...})` for account-routed (one server, per-entry tenant gate built in, fail-closed when auth principal can't be resolved).
- `createMediaBuyStore` — opt-in `targeting_overlay` echo on `get_media_buys` for sellers claiming `property-lists` / `collection-lists`.
- `MEDIA_BUY_TRANSITIONS` / `assertMediaBuyTransition` (and the creative pair) — canonical lifecycle graphs.

Worked reference adapters live in `examples/hello_*` — pick the one whose specialism matches yours and fork.

**v5 lower-level API** (still fully supported as the substrate the v6 path calls into):

```typescript
import type { CreateMediaBuyRequest, CreateMediaBuyResponse } from '@adcp/sdk';
import { CreateMediaBuyRequestSchema } from '@adcp/sdk/schemas';

function handleCreateMediaBuy(rawParams: unknown): CreateMediaBuyResponse {
  const request: CreateMediaBuyRequest = CreateMediaBuyRequestSchema.parse(rawParams);
  // request.buyer_ref, request.account, request.brand — all typed
}
```

Migration path from 6.6 → 6.7: see [`docs/migration-6.6-to-6.7.md`](docs/migration-6.6-to-6.7.md) (fifteen recipes, two breaking — `'implicit'`-resolution platforms now actually enforce the inline-`account_id` refusal the docstring has long claimed (pre-6.7 it was silent-pass, so audit your callers); `SalesPlatform` split into `SalesCorePlatform & SalesIngestionPlatform`). 5.x → 6.x: [`docs/migration-5.x-to-6.x.md`](docs/migration-5.x-to-6.x.md). Note: `PackageRequest` (creation-shaped, required fields) differs from `Package` (response-shaped). See the [type catalog](docs/ZOD-SCHEMAS.md#type-catalog) for all request types and their required fields.

## Multi-Agent Operations

Execute across multiple agents simultaneously:

```typescript
const client = new ADCPMultiAgentClient([agentX, agentY, agentZ]);

// Parallel execution across all agents
const results = await client.allAgents().getProducts({ brief: 'Coffee brands' });
// results: TaskResult<GetProductsResponse>[]

const agentIds = client.getAgentIds();
results.forEach((result, i) => {
  console.log(`${agentIds[i]}: ${result.status}`);

  if (result.status === 'completed') {
    console.log(`  Sync: ${result.data?.products?.length} products`);
  } else if (result.status === 'submitted') {
    console.log(`  Async: webhook to ${result.submitted?.webhookUrl}`);
  }
});
```

## Idempotency

Every mutating tool call (`createMediaBuy`, `syncCreatives`, `activateSignal`, etc.) auto-generates an `idempotency_key` (UUID v4) when the caller omits one. Internal retries reuse the key so a re-sent request returns the cached response rather than double-booking. See `docs/llms.txt` for the full protocol story.

```typescript
const result = await client.createMediaBuy({ account, brand, start_time, end_time, packages });

// Key used on the wire (auto-generated or caller-supplied). Log alongside your own IDs.
result.metadata.idempotency_key;

// true when the response was a cached replay. Side-effecting callers MUST gate
// notifications, memory writes, downstream calls on this flag.
result.metadata.replayed;
```

**Typed errors on replay conflicts** — check `result.errorInstance` with `instanceof` instead of switching on error codes:

```typescript
import { IdempotencyConflictError, IdempotencyExpiredError } from '@adcp/sdk';

if (result.errorInstance instanceof IdempotencyConflictError) {
  // Reconcile by natural key before deciding whether this is a new intent.
  // Do not blindly mint a fresh key: the prior operation may have succeeded.
}
if (result.errorInstance instanceof IdempotencyExpiredError) {
  // Key past the seller's replay window. Look up by natural key before retrying.
}
```

**BYOK** (persist keys across process restarts so crash-recovery can resend the exact key):

```typescript
import { useIdempotencyKey } from '@adcp/sdk';

// Validates against the spec pattern `^[A-Za-z0-9_.:-]{16,255}$` before the round-trip.
const key = await db.getOrCreateIdempotencyKey(campaign.id);
await client.createMediaBuy({ ...params, ...useIdempotencyKey(key) });

// Check the seller's replay window so you know when to fall back to natural-key lookup.
// Throws on v3 sellers that omit the declaration — the SDK does NOT default to 24h.
const ttlSeconds = await client.getIdempotencyReplayTtlSeconds();
```

Idempotency keys are retry-pattern oracles within their TTL, so the SDK truncates them to the first 8 characters in debug logs by default. Set `ADCP_LOG_IDEMPOTENCY_KEYS=1` to opt into full logging for local debugging.

**Crash recovery**: if your process dies mid-retry and you need to decide whether to re-send — look up the persisted key by natural key, check `result.metadata.replayed`, and handle `IdempotencyConflictError` / `IdempotencyExpiredError`. Worked recipe in [`docs/guides/idempotency-crash-recovery.md`](./docs/guides/idempotency-crash-recovery.md).

## Security

### Webhook Signature Verification

```typescript
const client = new ADCPMultiAgentClient(agents, {
  webhookSecret: process.env.WEBHOOK_SECRET,
});

// Signatures verified automatically on handleWebhook()
// Returns 401 if signature invalid
```

### Request Signing (RFC 9421)

AdCP 3.0 supports [HTTP Message Signatures (RFC 9421)](https://www.rfc-editor.org/rfc/rfc9421) for cryptographic request authentication. A buyer signs outbound requests so the seller can verify who sent them and that nothing was tampered with. A seller signs outbound webhooks so the buyer can verify authenticity. Optional in 3.0, mandatory in 3.1+ for mutating operations.

**Generate a signing key:**

```bash
adcp signing generate-key --alg ed25519 --kid my-agent-2026 \
  --private-out ./private.jwk --public-out ./public-jwks.json
# Publish public-jwks.json at your /.well-known/jwks.json endpoint.
# Point to it from your /.well-known/brand.json agents[].jwks_uri.
```

**Sign outbound requests (buyer):**

```typescript
import { createSigningFetch } from '@adcp/sdk/signing';

const signingFetch = createSigningFetch(fetch, {
  keyid: 'my-agent-2026',
  alg: 'ed25519',
  privateKey: privateJwk, // JWK with `d` field
});

await signingFetch('https://seller.example.com/mcp', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
// Signature, Signature-Input, and Content-Digest headers added automatically.
```

**Verify inbound signatures (seller):**

```typescript
import { createExpressVerifier, StaticJwksResolver, InMemoryReplayStore } from '@adcp/sdk/signing';

// Raw-body capture MUST be mounted ahead of the verifier — express.json()
// would otherwise consume the stream and the verifier would have no bytes to
// hash. `rawBodyVerify` comes from `createExpressAdapter()`; the inline form is
// equivalent.
app.use(express.json({ verify: adapter.rawBodyVerify }));
// or: app.use(expre...

© adcontextprotocol · Apache-2.0 · adcontextprotocol/adcp-client

Похожие плагины

Все плагины по теме «Настройка агента: память, контекст, субагенты» →
ponytail DietrichGebert Плагин настраивает агента на короткие решения без лишних абстракций. Сгодится, когда нужно использовать возможности уже имеющегося кода и не усложнять задачу. ★ 177 259звёзд репо impeccable pbakaus Даёт агенту словарь дизайнера: 24 команды вроде polish, audit и bolder, чтобы правки интерфейса не сводились к «сделай красиво». ★ 89 321звёзд репо last-30-days-skill mvanhorn Собирает свежие посты, комментарии и транскрипты по любой теме за последний месяц и укладывает их в один отчёт. ★ 65 596звёзд репо claude-hud jarrodwatts Показывает под строкой ввода, сколько контекста занято, какие инструменты работают и как идут задачи. Пригодится, когда сессия длинная и надо видеть расход. ★ 28 914звёзд репо understand-anything Egonex-AI Плагин сканирует проект и строит интерактивный граф файлов, классов и зависимостей с веб-дашбордом. Пригодится при знакомстве с чужим или крупным кодом. ★ 88 429звёзд репо mem0 mem0ai Подключает долговременную память к Claude Code, сохраняя контекст проектов и привычки между сессиями. ★ 67 869звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой