Skip to main content
Glama
README.md
# paraswap-mcp

An MCP server that builds token swaps and hands them over **unsigned**.

It quotes a swap on [Paraswap](https://www.paraswap.io/) v5 and returns the raw
transactions (`to` / `data` / `value`) for someone else to execute — in
practice, a [Rustok](https://github.com/rustok-org) self-custody wallet, where a
human approves every transaction in a separate console before anything is
signed.

**This server holds no keys, signs nothing and sends nothing on-chain.** It has
no filesystem state and no secrets. The worst it can do is return a transaction
you decline to sign.

Chains: Ethereum (1), Arbitrum One (42161), Base (8453).

## Why it exists

An agent that can swap tokens usually needs a private key sitting in `.env`.
The alternative on offer is usually an agent that cannot touch funds at all.

The middle rung — the agent acts on its own, but a human releases every
transaction — needs the building of a transaction to be separate from the
signing of it. This server is the building half. The signing half belongs to a
wallet the agent does not control.

## Install

> The published image lands with the first release; until then, run from source
> (bottom of this section) — it needs nothing but Python 3.12+.

Pull the image (no Python install needed):

```bash
docker pull ghcr.io/rustok-org/paraswap-mcp:1.0.0
```

Register it as a stdio MCP server — for example, in an MCP client config:

```json
{
  "mcpServers": {
    "paraswap": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "ghcr.io/rustok-org/paraswap-mcp:1.0.0"]
    }
  }
}
```

Podman works the same way; substitute `podman` for `docker`.

Running from source needs nothing but Python 3.12+ — the server imports only the
standard library:

```bash
python3 paraswap_mcp.py
```

## Configuration

Both variables are optional. Defaults are the three chains above.

| Variable | Meaning |
| --- | --- |
| `PARASWAP_ALLOWED_CHAINS` | Comma-separated chain ids the server will serve, e.g. `1,42161`. **An empty or malformed value refuses to start** — a server with zero chains is never intentional. To get the defaults, leave the variable unset. |
| `PARASWAP_RPC_URLS_<id>` | JSON-RPC endpoint used for the on-chain allowance check on that chain, e.g. `PARASWAP_RPC_URLS_1`. Public nodes are used when unset. Allowing a chain with no default and no override refuses to start. |

## The tool

`build_swap_transaction` — one call, one swap plan.

Input: `chain_id`, `src_token`, `src_decimals`, `dest_token`, `dest_decimals`,
`amount` (human units, e.g. `"5"`), `user_address`. Optional: `slippage_bps`
(default 250 = 2.5%), `src_symbol` / `dest_symbol`.

Output: a fresh quote, the allowance check, and an **ordered** list of raw
transactions. When the wallet's current allowance to the Paraswap
`TokenTransferProxy` is short, an **exact-amount** ERC-20 approve is prepended —
never an unlimited one. If a *partial* allowance is already in place, a reset to
zero comes first: USDT and the tokens that copied it revert an approve made over
a non-zero allowance. Execute the transactions in the order returned.

```jsonc
{
  "chain": { "id": 42161, "name": "Arbitrum One" },
  "quote": {
    "src_amount": "5",
    "src_token": { "address": "0xaf88…5831", "chain_id": 42161 },
    "dest_amount_estimate": "0.002663",
    "dest_token": { "address": "0x82aF…Bab1", "chain_id": 42161 },
    "slippage_bps": 250,
    "note": "quote is fresh now and expires in minutes; execute promptly"
  },
  "allowance_check": { "spender": "0x216b…fcae", "approve_included": true },
  "transactions": [ { "purpose": "approve exactly …", "to": "…", "data": "0x…", "value": "0", "chain_id": 42161 } ]
}
```

## Things this server deliberately does not promise

Read these before putting it in front of real money.

- **Quotes go stale in minutes.** Build immediately before executing. If a human
  approval is going to take a while, build again afterwards rather than
  executing an old plan.
- **`src_symbol` / `dest_symbol` are your own labels, echoed back unverified.**
  They are never read from the contract, and every echoed symbol is returned
  alongside `"symbol_unverified": true`. The **address** is the identity of a
  token; a screen shown to a human must not present these symbols as confirmed
  fact.
- **The spender comes from the live quote, never from a hardcoded address.** The
  Paraswap `TokenTransferProxy` differs per chain, and approving the wrong
  contract wastes gas at best.
- **A wallet reporting "executed" means the transaction was broadcast, not that
  it succeeded on-chain.** Verify the receipt independently (an explorer, or
  `eth_getTransactionReceipt` — `status` must be `0x1`) until the wallet you use
  checks receipts itself. A reverted swap still costs gas.
- **Prices, routes and liquidity come from Paraswap.** This server does not
  audit them, and does not compare against other venues.

## Development

```bash
uv run --with ruff ruff check paraswap_mcp.py test_paraswap_mcp.py
uv run --with ruff ruff format --check paraswap_mcp.py test_paraswap_mcp.py
uv run --with mypy mypy --strict paraswap_mcp.py
uv run --with pytest pytest -q
```

Tests never touch the network: the HTTP boundary is the only thing stubbed, and
the swap-planning logic is exercised for real.

## License

MIT-0 — see [LICENSE](LICENSE). Use it without attribution.

TDQS

A4.5/5.0

Scored across 1 tool

Disambiguation5/5

Only one tool exists, so there is no possibility of confusion between tools. The tool's purpose is clearly and specifically stated.

Naming Consistency5/5

The single tool uses a clear verb_noun pattern ('build_swap_transaction'). There are no other tools to introduce inconsistency.

Tool Count2/5

One tool is too few for a server that might be expected to provide a broader range of swap-related operations (e.g., quoting, token info). The scope feels thin.

Completeness4/5

The tool covers the core swap workflow by returning ordered transactions including approval and swap. However, it lacks separate quote retrieval or token discovery capabilities, which are minor gaps for a swap-focused server.

Maintenance

ActivitySlowing
ResponsivenessNo issues