Skip to main content
Glama
mannutech
by mannutech
README.md
# o1-mcp

[![CI](https://github.com/mannutech/o1-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/mannutech/o1-mcp-server/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

An MCP server that exposes the [o1.exchange](https://o1.exchange) DEX aggregator — live swap quotes and routing on **Base** — to any MCP client (Claude Desktop, Claude Code, …). **Read-only and safe by default:** no private keys, no signing, no broadcasting. It returns routing data and, optionally, **unsigned** calldata you sign in your own wallet.

## Tools

| Tool | Endpoint | What it does |
|---|---|---|
| `o1_health` | `GET /health` | Liveness check. No auth. |
| `o1_get_quote` | `POST /quote` | **Primary.** Quote a swap → compact route summary + `raw`. |
| `o1_list_supported_dexes` | — | The DEX venues the router can use on Base. |
| `o1_build_swap_transaction` | `POST /submit` | **Gated.** Turn a `quoteId` into **unsigned** calldata. |
| `o1_execute_swap` | `POST /execute` | **Gated.** One-shot quote + unsigned calldata. |

The two gated tools register only when `O1_ENABLE_BUILD_TX=true`. They never sign or broadcast — you do, in your own wallet.

**`o1_get_quote`** takes `tokenIn`, `tokenOut` (an EVM address or `"ETH"`), and `amountIn` (base units / wei, as a string `> 0`). Optional: `slippageBps` (default `100`, capped at 2000 unless `O1_ALLOW_HIGH_SLIPPAGE=true`), `chainId`, `maxHops`, `splitEnabled`, `allowedDexes`, `feeBps`, `timeBudgetMs`. It returns:

```jsonc
{
  "quoteId": "…", "expiresAt": 1782700000000,
  "expectedAmountOut": "…", "minAmountOut": "…",
  "feeBps": 5, "gasUnits": "210000",
  "venues": ["AERODROME_V2LIKE", "UNIV3"],
  "routesCount": 2,
  "note": "quote expires ~10s",
  "raw": { /* full /quote response */ }
}
```

> **Selling ETH?** Set `useNativeIn: true` on the gated tools, or the calldata's `value` comes back `0`. Quotes expire in ~10s and are never cached.

## Demo

Verified live in **Claude Code** — the read-only tools load and respond against production:

![o1 tools registered in Claude Code](docs/demo-tools.png)

![o1_health returns ok:true and o1_list_supported_dexes lists 21 venues on Base](docs/demo-session.png)

> `o1_health` → ✅ `/health` returned `{ ok: true }` · `o1_list_supported_dexes` → **21 DEX venues on Base** (chain ID 8453). The gated `o1_build_swap_transaction` and `o1_execute_swap` tools appear once `O1_ENABLE_BUILD_TX=true`.

<details>
<summary>Text version</summary>

```text
Tools for o1 — 3 tools
  1. o1_health                read-only, open-world
  2. o1_get_quote             read-only, open-world
  3. o1_list_supported_dexes  read-only
```

`o1_list_supported_dexes` → `UNIV2`, `UNIV3`, `UNIV4`, `AERODROME_V2LIKE`, `AERODROME_CL`, `PANCAKE_V2`, `PANCAKE_V3`, `PANCAKE_INFINITY_CL`, `HYDREX`, `QUICKSWAP_V4`, `ALIEN_BASE_V3`, `CURVE`, `PROPSWAP`, `TESSERA`, `ELFOMOFI`, `LUNARBASE`, `FELTIR`, `DODO_V2`, `WOOFI`, `GYROSCOPE_ECLP`, `MAVERICK_V2`.

</details>



## Quick start

Requires **Node 20+**.

```bash
npm install
npm run build
cp .env.example .env   # set O1_API_KEY and O1_API_BASE_URL
```

Add it to your MCP client (stdio):

```jsonc
{
  "mcpServers": {
    "o1": {
      "command": "node",
      "args": ["/absolute/path/to/o1-mcp/dist/index.js"],
      "env": {
        "O1_API_BASE_URL": "https://quiet-bloodhound-531.convex.site",
        "O1_API_KEY": "your-key-here"
      }
    }
  }
}
```

Claude Code: `claude mcp add o1 -- node /absolute/path/to/o1-mcp/dist/index.js`.

### Environment

| Var | Required | Default | Notes |
|---|---|---|---|
| `O1_API_BASE_URL` | ✅ | — | Aggregator base URL, no trailing slash. Prod: `https://quiet-bloodhound-531.convex.site`. |
| `O1_API_KEY` | for non-health | — | Sent as `x-api-key` (issued by o1). Redacted from all output. |
| `O1_DEFAULT_SLIPPAGE_BPS` | | `100` | Default slippage (bps) when a quote omits it. |
| `O1_ALLOWED_CHAIN_IDS` | | `8453` | Chain allowlist. Base only (phase 1). |
| `O1_ENABLE_BUILD_TX` | | `false` | Register the gated `/submit` + `/execute` tools. |
| `O1_HTTP_TIMEOUT_MS` | | `8000` | Per-request timeout (ms). |
| `O1_MAX_RETRIES` | | `2` | Retries for `/health` & `/quote` on 429/5xx. |
| `O1_ALLOW_HIGH_SLIPPAGE` | | `false` | Allow slippage above the 2000 bps cap. |
| `O1_LOG_LEVEL` | | `info` | `debug`/`info`/`warn`/`error`. Logs go to stderr only. |

## Safety

- **Read-only by default** — swap-building tools are opt-in via `O1_ENABLE_BUILD_TX`.
- **No private keys, ever** — no tool accepts one, and output is always **unsigned**.
- **Inputs validated before any network call** — address format, `amountIn > 0`, slippage cap, chain allowlist.
- **Secrets redacted** from every log and error; **logs go to stderr only** (stdout carries only the MCP stream).
- **Errors mapped** — `400` invalid input · `401` auth · `429` rate-limited (with backoff) · `5xx` upstream. Reads (`/health`, `/quote`) retry with backoff + jitter; writes (`/submit`, `/execute`) never do.

## Development

```bash
npm run typecheck   # strict tsc
npm run lint        # eslint
npm test            # vitest (mocked fetch — no live network)
npm run dev         # run from source via tsx
```

## License

MIT

TDQS

A4.4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: quotes, health check, and listing supported DEXs. No overlap or ambiguity.

Naming Consistency4/5

All tools follow the 'o1_' prefix with descriptive names. While 'o1_health' is a noun instead of verb_noun, the pattern is still clear and consistent.

Tool Count5/5

Three tools is appropriate for a focused read-only aggregator interface. Each tool serves a necessary function without excess.

Completeness4/5

Covers core read operations (quote, health, supported DEXs) for the aggregator. Missing swap execution or token details, but the scope is explicitly read-only.

Maintenance

ActivityStale
ResponsivenessNo issues