There are two ways to give an AI agent SwapZilla, and the fast one is
already published. swapzilla-mcp is an open-source
(MIT, Node 18+) Model Context Protocol server that runs in one line and needs
no key — that is /mcp/. Build your own instead when the
swaps have to carry your partner key, your allow-list and your
spending limits.
- Ready-made: six tools, no account, no key, non-custodial by design.
- Your own: orders tagged with your
PartnerIDand yourclient_order_id. - Either way, creating an order moves no money — it returns an address.
- The key, if there is one, lives in the server process and never in the model's context.
The published server, in one line
npx -y https://swapzilla.io/mcp/swapzilla-mcp-1.0.0.tgz
Register it with any MCP client — Claude Desktop, Claude Code, Cursor, Windsurf — and the agent gets six tools:
| Tool | What the agent does with it |
|---|---|
swapzilla_list_assets | Resolve the exact asset code and network before anything else. |
swapzilla_list_providers | See which exchanges are aggregated and enabled. |
swapzilla_get_quote | Compare live offers across every provider; each carries an ID. |
swapzilla_validate_address | Check the recipient address for that network first. |
swapzilla_create_exchange | Open the swap and return a DepositAddress. |
swapzilla_get_order | Poll status through to the payout transaction hash. |
Two environment variables configure it: SWAPZILLA_API_BASE
(defaults to https://api.swapzilla.io) and an optional
SWAPZILLA_API_KEY, which it sends as X-API-Key. Public
rate data needs neither. Install instructions, the client config block and the
package itself are on /mcp/.
When to host your own instead
The published server is deliberately plain: it is the aggregator, exposed as tools, for whoever is running the agent. A partner integration usually wants things it does not have — and every one of them is a reason to wrap the partner API yourself.
| You need | Why your own server |
|---|---|
| The swaps to be yours | Calls made with a partner key are tagged with your PartnerID, and GET /v1/partner/orders lists them — attribution and reconciliation you cannot get from an agent's own machine. |
| An allow-list of destinations | An agent that can name any to_address can pay anyone. The check belongs in a tool you control, not in a prompt. |
| Spending caps | Per intent and per day, enforced in code before the order is created. |
| A human in the loop | Your create_swap can block on an approval your product already has. |
| Your own identifiers | client_order_id ties each swap to the intent, ticket or user that caused it. |
| Safety against retries | An Idempotency-Key per intent turns a retried tool call into the same order, not a second swap. |
Your own server, in about fifty lines
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const BASE = "https://api.swapzilla.io";
const KEY = process.env.SWAPZILLA_KEY!; // stays in this process
const server = new McpServer({ name: "acme-swaps", version: "1.0.0" });
server.tool(
"quote_swap",
{
from_asset: z.string(), from_network: z.string(),
to_asset: z.string(), to_network: z.string(),
amount_from: z.string().optional(), amount_to: z.string().optional(),
rate_type: z.enum(["floating", "fixed"]).default("floating"),
},
async (args) => {
const r = await fetch(`${BASE}/v1/partner/quotes?${new URLSearchParams(args)}`, {
headers: { "X-API-Key": KEY },
});
const { offers } = await r.json();
// Hand back only what a model should reason about, not the whole payload.
return { content: [{ type: "text", text: JSON.stringify(offers.slice(0, 5).map((o) => ({
id: o.ID, provider: o.ProviderName, receive: o.ToAmount,
minutes: o.EstimatedMins, kyc: o.KycRating, worse_by_percent: o.DeviationPercent,
}))) }] };
},
);
await server.connect(new StdioServerTransport());
The writing tool is the same shape with the guardrails wrapped around it:
server.tool(
"create_swap",
{ offer_id: z.string(), to_address: z.string(), intent_id: z.string() },
async ({ offer_id, to_address, intent_id }) => {
assertAllowed(to_address); // your allow-list
await requireApproval(intent_id); // your human, or your policy
const r = await fetch(`${BASE}/v1/partner/orders`, {
method: "POST",
headers: {
"X-API-Key": KEY, "Content-Type": "application/json",
"Idempotency-Key": intent_id, // one intent, one order
},
body: JSON.stringify({
offer_id, to_address,
refund_address: process.env.TREASURY_ADDRESS,
client_order_id: intent_id,
}),
});
const order = await r.json();
return { content: [{ type: "text", text:
`Send exactly ${order.AmountFrom} ${order.FromAsset} on ${order.FromNetwork} ` +
`to ${order.DepositAddress}. Order ${order.ID}.` }] };
},
);
The read-only tools — assets, quotes, address validation, status — are safe to let an agent call freely. Only the one that creates an order needs a gate in front of it.
Guardrails worth having
- Allow-list the destination. Restrict
to_addressto addresses your system already knows, and reject the rest inside the tool. - Cap the amounts. Per intent and per day, checked in the server. A model's judgement is not a spending limit.
- One intent, one idempotency key. Agents retry; the key is what keeps a retry from becoming a second swap.
- Quote and confirm inside five minutes. Offers expire, so a plan approved twenty minutes later must be re-quoted — and the tool should say so rather than silently repricing.
- Never put the key in the model's context. It lives in the server process, is never returned by a tool and never rendered into a prompt.
- Log
ProviderOrderID. It is the reference the provider's support recognises when a swap needs chasing.
The strongest guardrail is structural. The API is non-custodial: creating an order produces a deposit address and nothing else. Unless the same agent can also send the deposit, the worst it can do is open an order nobody funds — which expires by itself.
What neither server can do
| Not available | What that means |
|---|---|
| Push notifications | No webhooks — status is a polled tool call, and a waiting agent should poll every 5–10 seconds, not faster. |
| Cancellation | An order cannot be cancelled through the API. An unfunded one expires on its own. |
| Moving funds | By design. Every swap is funded by a wallet outside the agent's reach. |
| A sandbox | Not in production; for deterministic tests we can enable the mock provider on your key. |
| Wallet risk scoring | No AML or graph analysis anywhere in the API, so an agent cannot ask whether an address is risky. |
Questions
Does SwapZilla have an official MCP server?
Yes — swapzilla-mcp, open-source under MIT, running on Node 18+ and installed in one line with npx -y https://swapzilla.io/mcp/swapzilla-mcp-1.0.0.tgz. It exposes six tools and needs no account or API key for public rate data. Everything about installing it is on /mcp/; this page is about hosting your own when you need your key and your limits in the loop.
When is the published server not enough?
When the swaps have to be attributed to your partner id, when destinations must come from an allow-list, when spending needs a cap or a human approval, or when each swap has to carry your own client_order_id for reconciliation. All of that lives in the server process, so it means running your own.
Can an AI agent spend my money through this API?
Not on its own. The swap is non-custodial: creating an order returns a deposit address and moves nothing. Funds move only when a wallet sends that deposit, which is a separate action outside the API. Keep the deposit-sending capability away from the agent and the worst case is an unfunded order that expires.
How do I stop an agent from retrying itself into two swaps?
Give every intent an id and pass it as the Idempotency-Key when creating the order. A repeated call with the same key returns the original order rather than opening a second one.
How long is a quote valid for an agent to act on?
Five minutes. After that, creating an order with the offer id fails with 400 and the tool has to re-quote. Surface that expiry to the model so a slowly approved plan gets re-priced rather than failing mid-execution.
Should the agent see the raw API responses?
Better not. Offers carry more fields than a model needs; returning a trimmed shape — provider, amount received, minutes, KYC rating, how much worse than the best — keeps reasoning cheap and stops incidental values from turning into hallucinated guarantees.
What else the same key builds
- Crypto payment links and invoicesPay-by-link and invoicing for freelancers and contractors — quote by the amount due, settle straight to the payee's wallet.
- Crypto payment processing and acquiringCheckout for shops and SaaS: accept 100+ assets, settle in one, reconcile by your own order id.
- Crypto exchange API, SDK and swap widgetEmbed swaps in a wallet, bot, aggregator or website — streaming quotes, one order call, your own UI.
- Crypto payroll for contractors and remote teamsRecurring payouts to a contractor roster: each person's own asset and network, exact net amounts, per-run reconciliation.
- Multi-send: batch payouts to many addressesOne-off fan-out to hundreds of addresses: cross-chain, per-recipient status, safe retries.
- Streaming quotes, live rates and price alertsServer-sent quote streaming, an always-fresh rate feed and order tracking you can turn into alerts.
Getting a key
Register in the partner cabinet and your partner id and the api_key that goes in X-API-Key are issued at once. The key starts working once it is activated: write to @swapzilla_support_bot with a sentence about what you are building. Every endpoint, field and status is in the partner API reference.