DMC-12 dmc-12 / dmc-12 guide
UCP Merchant Binding · DMC-12 v1.0

Deployment onboarding

Partner (write-surface) access to a Merchant Binding deployment is granted by the merchant, not self-served: the deployment provisions a machine-to-machine OAuth client and seeds an agent row for you. This page documents the pattern; every concrete value below uses the spec's fictional merchant (Aurora Motors Group) — a real deployment publishes its own specifics.

1. Contact the merchant

Onboarding starts with the merchant's published partner contact — the merchant.contact block of its /.well-known/ucp manifest (in the example manifest: partners@auroramotors.example). Include your legal entity, a named technical contact, your use case, and the scopes you need.

2. The credential bundle

The deployment delivers these values over a secure channel (a time-bounded secret share — never plain email or chat).

FieldValue
Auth tenantauth.auroramotors.example (from the deployment's RFC 9728 metadata)
Token endpointhttps://auth.auroramotors.example/oauth/token
Grant typeclient_credentials
Audiencethe deployment's resource identifier (RFC 9728 resource)
Signing algRS256
Token TTL3600 s (1 hour)
client_id / client_secretunique per partner — delivered securely
Your sub at the service<client_id>@clients
A2A endpointMCP_BASE/a2a/ — from the manifest, e.g. https://mcp.auroramotors.example/a2a/
Two JWKS — don't confuse them The authorization server's JWKS verifies the access tokens it issues. The service JWKS (MCP_BASE/.well-known/jwks.json) verifies the Agent Card signature. You consume both, for different things.

3. Mint a token

A2A_BEARER=$(curl -s -X POST https://auth.auroramotors.example/oauth/token \
  -H 'content-type: application/json' \
  -d '{
    "client_id":     "<your client_id>",
    "client_secret": "<your client_secret>",
    "audience":      "<the deployment resource identifier>",
    "grant_type":    "client_credentials"
  }' | jq -r .access_token)

The authorization server returns a standard client-credentials token response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6…",   // the RS256 JWT — send as Bearer
  "expires_in": 3600,                                  // seconds; re-mint before this elapses
  "token_type": "Bearer",
  "scope": "inventory:read quote:write reservation:write deal:handoff pricing:read"
}
FieldTypeDescription
access_tokenstringThe RS256 JWT. Send as Authorization: Bearer <access_token> on every /a2a/ call.
expires_inintLifetime in seconds (3600). Cache and re-mint before it lapses.
token_typestringAlways Bearer.
scopestringSpace-delimited granted scopes. Your effective set at call time is this ∩ your agent row (see §5).

Cache the token for most of its TTL and re-mint before expiry. Minting on every request will get you rate-limited at the authorization server.

4. Call a tool

curl -X POST https://mcp.auroramotors.example/a2a/ \
  -H "Authorization: Bearer $A2A_BEARER" \
  -H 'content-type: application/json' \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "tools/call",
    "params": { "name": "search_inventory",
                "arguments": { "query": "2024 Outback AWD under 35k", "limit": 5 } }
  }'

The dry-run that completes onboarding is exactly this call: you mint a token and hit search_inventory; the deployment confirms your trace_id in its audit log and flips your agent row to active = true.

5. The scope model

The default partner bundle is five scopes (negotiation-enabled deployments add quote:negotiate). Deployments grant the minimum that covers the stated use case.

ScopeUnlocksPII?
inventory:readsearch_inventory, get_vehicle_by_vin, list_inventory, check_availabilityno
quote:writerequest_quoteno (opaque buyer_ref)
reservation:writecreate_reservation, release_reservation, get_reservation_statusno (opaque customer_ref)
deal:handoffinitiate_deal_handoffyes — name, phone, email
pricing:readget_pricing_disclosure (+ OTD on quotes)no
Effective scope = DB row ∩ JWT grant Your permissions at call time are the intersection of the scopes seeded on your agent row and the scopes present in the token you minted. A token requested with only inventory:read cannot reserve, even if your row grants reservation:write. Mint tokens with exactly the scopes the session needs. An empty intersection returns NOT_AUTHORIZED.
quote:negotiate is subset-dependent The negotiation scope is only meaningful on deployments that declare the negotiation subset on their manifest and conformance claim. On a deployment without it, the scope is not issued and the four negotiation tools are not registered.

6. Rate limits & audit

BudgetDefaultCeiling (on request)
Requests / minute (burst)60600
Reservations / day5100
Audit retention90 days365 days (with agreement)

Rate-limit denials come back in the envelope as RATE_LIMITED (retryable), not as a JSON-RPC error. Every response carries _metadata.trace_idlog it; it is the only way the merchant can correlate a complaint against its audit trail. Quote your trace_id and any error_id in incident reports. The budgets above are the pattern's reference defaults — each deployment publishes its own.

Backoff on RATE_LIMITED Treat RATE_LIMITED as retryable: exponential backoff with jitter (e.g. ~1s, 2s, 4s … capped, plus a random fraction) and a sane max-attempt ceiling — don't hot-loop. Stay under your per-minute burst rather than relying on retries. Note the reservation budget is separate from the rate limit: create_reservation charges your daily reservation budget, and a same-UTC-day release_reservation refunds that slot — so a held-then-released VIN within the day costs nothing against the budget. See the error taxonomy for which codes are retryable.

7. Lifecycle

Reference material Spec source: github.com/mm-open/dmc-12 · served spec: dmc12.ai/specification/SPEC.md · schemas: dmc12.ai/schemas/{capability}.json.
copied