Skip to content
Integra Agentic Terms

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.

@integraledger/terms-mcp serves the buyer gate's operations as Model Context Protocol 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:

{
  "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:

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 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):

{
  "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.

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 and 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.

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:

import {  } from "@modelcontextprotocol/server/stdio";
import type { Signer } from "@integraledger/terms";
import {  } from "@integraledger/terms-mcp";

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

(() => ({ : globalThis., ,  }));

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 lists every tool's arguments, results and annotations.

Edit on GitHub

Last updated on

On this page