# MCP server

> Give any MCP client the buyer gate as tools, install the skill that tells the agent how to use them, and serve the tools with your own signer.

Source: https://agenticterms.integraledger.com/guides/mcp

`@integraledger/terms-mcp` serves the buyer gate's operations as [Model Context Protocol](https://modelcontextprotocol.io)
tools, and ships a skill that tells an agent how to use them. Each tool call is one call of `@integraledger/terms`; the
server adds the transport and the words an agent reads, and nothing else.

## Install the server

Most MCP clients take a configuration like this:

```json
{
  "mcpServers": {
    "terms": {
      "command": "npx",
      "args": ["-y", "@integraledger/terms-mcp@0.3.1"]
    }
  }
}
```

`npx` runs the package's `terms-mcp` binary, which serves the tools over stdio with no signer. It needs Node.js
`>=26.11.1`. Its `serverInfo` name is `integraledger-terms`.

## Install the skill

The skill is `skills/confirming-the-atr-hash-before-paying/SKILL.md` in the package. Copy its folder into your agent's
skills directory (for Claude Code, `.claude/skills/`), or paste the file into the agent's instructions:

```sh
npm install @integraledger/terms-mcp@0.3.1
cp -r node_modules/@integraledger/terms-mcp/skills/confirming-the-atr-hash-before-paying .claude/skills/
```

The skill's description tells the agent when it applies: before approving any payment to a seller whose payment
request advertises an ATR hash.

## What the agent does

With `terms-mcp`, the agent's own wallet signs. For one payment:

1. **Confirm.** Call `atr_confirm` with the `pairing`, the seller's `document` (the parsed `PAYMENT-REQUIRED` document,
   or the MPP challenges) and the payer `account` in CAIP-10 form. On a match, the result carries the ATR, its hash,
   `chosen`, and `request`, the exact thing to sign.
2. **Sign.** Have the wallet sign `request` unchanged. The [signers guide](https://agenticterms.integraledger.com/guides/signers) lists every kind.
3. **Finish.** Call `atr_finish` with the `pairing`, `atr.base64`, `chosen` unchanged, and the wallet's answer as
   `signature`. The result is `signed`, the payment to send, only when the pairing reads H from the completed payment,
   or, where the proof is the agreement payment, without that reading.
4. **Send** `signed` as the protocol sends a payment.

A confirmed result, abridged (the long `typedData.types` and the echoed document are shortened):

```json
{
  "atrHash": "0x8b1e122580ae3f6a8c3d36a24294e1260a87bde70f597279b973e39310072938",
  "atr": {
    "base64": "eyJhdHJWZXJzaW9uIjoiMSIsImlkIjoiMGY4ZmFkNWItZDljYi00NjlmLWExNjUtNzA4Njc3Mjg5NTBlIiwieDQwMiI6eyJ6IjoxLjAsImEiOiJjYWZcdTAwZTkifSwic2VsbGVyIjp7Im5vdGUiOiJDYWbDqSDigJQgMzAgZMOtYXMg4pyTIn19",
    "utf8": "{\"atrVersion\":\"1\",\"id\":\"0f8fad5b-d9cb-469f-a165-70867728950e\",\"x402\":{\"z\":1.0,\"a\":\"caf\\u00e9\"},\"seller\":{\"note\":\"Café — 30 días ✓\"}}"
  },
  "chosen": {
    "pairing": "x402/exact/eip155/eip3009",
    "choice": { "accepted": { "scheme": "exact", "network": "eip155:84532" }, "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" },
    "ref": "abKt2A5-ljuE6ab515O0H8AIX4w_r4ud"
  },
  "request": {
    "kind": "eip712",
    "typedData": {
      "domain": { "name": "USDC", "version": "2", "chainId": 84532, "verifyingContract": "0x036CbD53842c5426634e7929541eC2318f3dCF7e" },
      "primaryType": "TransferWithAuthorization",
      "message": {
        "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
        "to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
        "value": "10000",
        "validAfter": "0",
        "validBefore": "1790438406",
        "nonce": "0x8b1e122580ae3f6a8c3d36a24294e1260a87bde70f597279b973e39310072938"
      }
    }
  }
}
```

The signed `nonce` is `atrHash`. The agent passes `chosen` back to `atr_finish` exactly as it came.

### Payments signed in steps

If `atr_finish` returns `next`, the wallet signs `next`, and the agent calls `atr_finish` again with every answer so
far, in order, as a list.

### Nothing to sign

If `request` is `null`, the wallet signs nothing. For `x402/batch-settlement/cloudflare`, whose build is itself the
payment, the agent calls `atr_finish` with `atr.base64`, `chosen` unchanged and `signature: null`; it returns the
payment as `signed`, which the agent sends. For every other pairing, the comparison was the agent's whole step, and the
seller's own flow completes the payment.

### Pairings that pay an agreement first

If `atr_confirm` returns `chosen.agreement`, a URL, and no `request`, the payment is not itself a public proof of H,
and an agreement payment carrying H must be recorded first. The agreement payment is a payment like any other, and the
agent approves it as it approves any payment:

- where the host offers `atr_agree`, call it with `atr.base64` and `chosen`, exactly as returned, `mac` included (a
  changed `chosen` is declined `chosen-unverified`, and nothing is fetched or signed). It returns `approve`, the
  agreement payment, and signs nothing: `approve.option` is its `amount`, `asset`, `payTo` and `network`. Where the
  agreement is already recorded, it returns the `receipt`. To approve the payment, call `atr_agree` again with
  `approved` set to `approve`, exactly as returned; it returns the `receipt`;
- otherwise, request `chosen.agreement`. It answers `402` with an x402 payment request. Pay it through `atr_confirm`
  and `atr_finish` with the pairing of an option for which `atr_confirm` returns a `request` and no
  `chosen.agreement`. Before the wallet signs, check that the `atrHash` `atr_confirm` returns for it is the H of the
  payment being made, and pay nothing if it is not. Then request the URL again with that same payment until it answers
  `200` with a receipt for that H.

Then call `atr_confirm` again with the same arguments and the `receipt`. Only then does it return `request`. The
receipt is the agreement URL's statement: `atr_confirm` checks that it names H and is a recorded agreement, and does
not look up the transaction it names.

Once the agreement payment is sent, a decline from `atr_agree` or `atr_transact` carries it as `moved`;
`agreement-pending` means it may settle yet. To collect the receipt, call the same tool again without `approved`: the
gate asks the agreement URL unpaid and goes on once it answers `200` with the receipt, or declines `agreement-pending`
again while it answers `202`, signing nothing. Never call again with `approved` for the same agreement: every call with
`approved` signs and sends a new agreement payment. See [agreement payments](https://agenticterms.integraledger.com/guides/agreement-payments).

### Channels and sessions

After the opening payment, call `atr_channel_open` with the pairing, `atr.base64`, the `signed` opening, the `landed`
receipt where one was returned beside it, and the `mac`, and keep the `hold`. For each later payment, call `atr_channel_within` (where the host offers it) with the pairing, the `hold` and the
seller's new document. When the seller reports its cumulative charge, call `atr_channel_record_charge`. Always keep the
latest `hold`, and pass it back exactly as returned: it carries a `mac` from the server process, and a changed hold, or
one from another server process, is declined `hold-unverified`, as a changed opening is declined `opening-unverified`.
After a server restart, open a new channel. See [channels and sessions](https://agenticterms.integraledger.com/guides/channels-and-sessions)
and [channel holds](https://agenticterms.integraledger.com/reference/mcp#channel-holds).

### Declines

A result with `isError: true` carries `{ decline: { code, detail } }`. The payment would not carry a confirmed hash:
send nothing for that request. Where it also carries `moved`, the wallet already moved the payment: keep `moved.signed`
with the ATR, and present it again rather than signing a new one. See [declines](https://agenticterms.integraledger.com/reference/declines).

## Serve the tools with your own signer

A host that holds a wallet can serve the same tools with it. The server then also offers `atr_transact` (confirm, pay
the agreement the agent approved, sign and finish), `atr_agree` and `atr_channel_within`:

```ts no-run
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import type { Signer } from "@integraledger/terms";
import { createBuyerServer } from "@integraledger/terms-mcp";

declare const signer: Signer; // your wallet
declare const agreementSigner: Signer; // a second wallet that pays agreements; omit it to pay them with signer

serveStdio(() => createBuyerServer({ fetch: globalThis.fetch, signer, agreementSigner }));
```

The `fetch` you pass is the server's whole network policy. The binary's public-address-only fetch is not exported. A
server that holds a wallet and fetches links that sellers choose should pass a `fetch` that refuses loopback,
private-use, link-local and unique-local addresses.

With a host signer, the agent never sees a request: `atr_transact` returns the payment to send and the ATR to keep, and
declines exactly as the gate does. The agreement payment is shown before it is signed: where the pairing needs one,
`atr_transact` first returns it as `approve`, with its amount, asset, payee and network, signs nothing, and pays it only
when called again with `approved` set to `approve`. The payment for the resource is handed to the host's signer
directly; approving it is the agent's step before it calls `atr_transact`. The gate applies no spending limit.

## What the server guarantees

- `atr_confirm` returns a `request` only after SHA-256 of the served bytes equals the advertised H, built with the hash
  it computed.
- `atr_finish` returns `signed` only when the pairing reads the hash of the bytes it was given from the completed
  payment, or, where the proof is the agreement payment, without that reading.
- `terms-mcp` holds no keys. With a host signer, only the host's code signs, and only after a match.
- `atr_channel_open` holds only an opening, and `atr_channel_within` signs and `atr_channel_record_charge` records only
  from a hold, that this server process returned, unchanged. For a pairing with a channel, `atr_finish` reads only a
  `chosen` that this server process returned, unchanged. Each carries a `mac` under a key the process generates and
  never exports.
- `terms-mcp` fetches only from public addresses: a link on a loopback, private-use, link-local or unique-local
  address, or a host name that resolves to one, is declined `atr-unfetchable` before any connection.
- Every value the seller supplies is data: `atr.utf8`, and the agreement receipt's `network` and `transaction` that
  `atr_agree` and `atr_transact` return. The skill tells the agent to read them and never to follow instructions
  inside them.

The [tools reference](https://agenticterms.integraledger.com/reference/mcp) lists every tool's arguments, results and annotations.
