# MM by Escrow MCP — guide for AI agents

> MM is an escrow middleman for deals between strangers, people or AI agents. The buyer locks USDC on Base in Coinbase's audited Commerce Payments escrow contract (AuthCaptureEscrow, 0xf96815976523E00e65Be8f34cA5e64b4f41EB19c). From there it can only go to the seller once delivery is confirmed, or back to the buyer. MM checks delivery itself where the proof is public and settles disputes with a panel of AI models from different labs. An agent can run a whole deal without a person: open it, accept it, fund it from its own wallet, deliver, verify and get paid.

## Connect

- **MCP** (Streamable HTTP, stateless, no account, no API key): `https://escrowmcp.org/mcp`
  - Claude Code: `claude mcp add --transport http escrowmcp https://escrowmcp.org/mcp`
  - Any client: `{"mcpServers": {"escrowmcp": {"type": "http", "url": "https://escrowmcp.org/mcp"}}}`
  - MCP Registry name: `org.escrowmcp/mm` · Server Card: https://escrowmcp.org/mcp/server-card · AI Catalog: https://escrowmcp.org/.well-known/ai-catalog.json
- **HTTP/JSON** (same actions): OpenAPI 3.1 at https://escrowmcp.org/openapi.json
- Each deal has two private tokens, one per side. Every call carries yours (MCP: `token` argument; HTTP: `x-mm-token` header). Whoever holds a token acts as that side, so pass the counterparty's token only to the counterparty.

## Networks

- `base-sepolia` — test USDC (free, for practice) on Base Sepolia (testnet), deals $1–$1,000,000, open
- Card / bank / wire in US dollars through Stripe (test mode): `payment_rail: "card_or_bank"`. A person completes the Stripe pages.

Practice first: use `network: "base-sepolia"`. Free test USDC: https://faucet.circle.com or the Coinbase developer faucet.

## A deal, start to finish (USDC)

1. **Open** — `mm_create_deal` with your role, title, price, terms and (ideally) a delivery check. Keep `your_token`; give `counterparty_token` to the other agent.
2. **Accept** — the other side calls `mm_accept_terms`. The seller passes `payout_address` (the wallet to be paid). Either side can `mm_change_terms` before funding; the other side accepts again.
3. **Fund** — the buyer calls `mm_get_funding_request` with `payer_address`, signs the returned EIP-712 message with that wallet, and calls `mm_submit_funding_signature`. No gas: MM submits the transaction. The signature only authorizes this exact payment into this escrow, and it expires in 2 hours.
4. **Deliver** — the seller calls `mm_mark_delivered` with proof (a note, links, and for token deliveries the transaction hash).
5. **Get paid** — an automatic check passes, or the buyer calls `mm_accept_delivery`, or the inspection window ends. MM releases the payment to the seller's wallet minus its fee.
6. **If something is wrong** — `mm_open_dispute`, then both sides `mm_add_evidence` (set `ready_for_ruling` when done). A panel of independent models rules on the split; `mm_respond_to_ruling` accepts it or appeals once to a larger panel.

Every deal view tells you whose move it is: `your_move` (plain words), `your_move_tool` (which tool makes it) and `waiting_on` (`you`, `them`, `mm` or `none`). Poll `mm_get_deal`, or set a webhook with `mm_set_webhook`.

## Signing the funding message

`mm_get_funding_request` returns `typed_data` (EIP-712, ERC-3009 `ReceiveWithAuthorization` for USDC), `typed_data_v4` (ready for `eth_signTypedData_v4`) and `digest` (the 32-byte EIP-712 hash, for signers that only sign raw hashes).

JavaScript (viem):
```js
const fr = /* mm_get_funding_request result */;
const t = fr.typed_data_v4;
const { EIP712Domain, ...types } = t.types;
const signature = await account.signTypedData({
  domain: { ...t.domain, chainId: Number(t.domain.chainId) }, types, primaryType: t.primaryType,
  message: { ...t.message, value: BigInt(t.message.value), validAfter: BigInt(t.message.validAfter), validBefore: BigInt(t.message.validBefore) },
});
// then: mm_submit_funding_signature { deal_id, token, signature }
```

Python (eth-account):
```python
from eth_account import Account
from eth_account.messages import encode_typed_data
signed = Account.sign_message(encode_typed_data(full_message=fr["typed_data_v4"]), private_key)
signature = "0x" + signed.signature.hex()
```

Raw-hash signers (KMS, HSM, MPC): sign `digest` directly with secp256k1, no message prefix, and send the 65-byte r‖s‖v signature.

## Delivery checks MM runs itself

Set `check` on `mm_create_deal`. Objective checks release payment with no one clicking anything.

| check.type | MM confirms | Releases by itself |
|---|---|---|
| `domain` (`domain`) | the buyer's one-time code appears in the domain's public DNS | yes |
| `github` (`repo`, `buyer_login`) | the repository's new owner on GitHub | yes |
| `nft` (`chain`, `contract`, `token_id`) | the buyer's wallet holds the token | yes |
| `token_tx` (`chain`, `token`, `amount`) | a transfer from the seller to the buyer after funding (single use) | yes |
| `url` (`url`, `mode`: `control` or `contains`, `text`) | a proof code or agreed text on the page | control mode |
| `file` (`expected_sha256`) | the delivered file's fingerprint | no |
| `ai_review` | MM reads the delivery against the terms | no (the buyer decides) |
| `manual` | the buyer accepts, or the inspection window ends | — |

A check that was already true when the money was locked never pays out instantly; it starts the inspection window instead.

## Webhooks

`mm_set_webhook` (or `webhook_url` on create): MM POSTs every deal event as JSON. Verify header `x-mm-signature` = hex HMAC-SHA256 of the raw body, keyed by sha256("mm-party:" + your token) in hex.

## Fees

- USDC deals: 1% of the price, paid by the buyer, the seller, or split (`fee_paid_by`). MM is paid only on money released to the seller; a refund costs nothing.
- Card / bank deals: 3.5% + $4 per deal (the flat part covers Stripe's fixed costs).
- Launch limit on real money: deals up to $2,500 for now; it rises after 25 real deals close cleanly (0 so far). Test-network deals have no launch limit.
- `mm_quote` returns exact amounts before you open a deal.

## Rules MM follows

- The escrow contract lets MM do only two things with locked money: pay the seller named in the signed payment, or refund the buyer.
- The fee is capped inside the buyer's signature and paid to one fixed address.
- If the buyer doesn't dispute before the inspection window ends, MM pays the seller.
- If the seller misses the delivery deadline, MM refunds the buyer (plus any seller bond).
- After the on-chain expiry the buyer can call `reclaim()` on the contract directly; `GET /api/deals/{id}/receipt` returns that exact call.
- Every event goes into a hash-chained log: `GET /api/deals/{id}/log`.

## Tools

mm_info · mm_quote · mm_create_deal · mm_get_deal · mm_accept_terms · mm_change_terms · mm_get_funding_request · mm_submit_funding_signature · mm_mark_delivered · mm_check_delivery_now · mm_accept_delivery · mm_open_dispute · mm_add_evidence · mm_respond_to_ruling · mm_refund_buyer · mm_cancel_deal · mm_send_message · mm_set_webhook · mm_get_payment_link · mm_get_payout_setup_link · mm_buy_usdc_link

## Errors

MCP tool errors come back as `isError: true` with a plain-English reason ("That signature does not match this payment…"). HTTP errors return `{"error": "…"}` with a 4xx status. Nothing is half-done on an error; fix the input and call again.

## Trust and contact

- How the money is protected, and how a buyer reclaims without MM: https://escrowmcp.org/protection
- Every finished real-money deal, no names: https://escrowmcp.org/record (JSON: https://escrowmcp.org/api/record)
- Site terms (version 2026-11-10, recorded on every deal): https://escrowmcp.org/terms · Privacy: https://escrowmcp.org/privacy · Not allowed: https://escrowmcp.org/prohibited
- A person to write to: support@escrowmcp.org

More: https://escrowmcp.org/agents (human-readable) · https://escrowmcp.org/research (the agent-escrow landscape)
