An agent can talk to Routey two ways: through the MCP server, which is ergonomic for LLM-driven workflows, or through the REST API, which is ergonomic for everything else. Both call the same library code that the web UI calls.
Surface
Transport
Use when
MCP server
stdio, JSON-RPC
An LLM agent (Claude Code, Claude Desktop, any MCP client) needs to call swap tools with typed inputs and structured outputs.
REST API
HTTP/JSON + SSE
A bot, a trading script, a dashboard, or any non-MCP runtime needs to fetch quotes, execute swaps, or consume a live event stream.
MCP server
Installation
The server is a single TypeScript file at mcp/index.ts. Run it with npx tsx over stdio. Any MCP client can host it.
Claude Code
terminal
claude mcp add routey -- npx tsx /absolute/path/to/routey/mcp/index.ts
Verify the server is reachable:
terminal
claude mcp list
Claude Desktop
Add an entry to your claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/ — Windows: %APPDATA%/Claude/):
The server announces itself as routey v1.0.0 and exposes four tools (listed below).
Signing key
get_quote, get_supported_chains, and get_supported_tokens are read-only and require no key.
execute_swap needs a private key to sign the transaction. Two ways to supply it, in priority order:
Pass it explicitly as the private_key tool argument. Used for the exact call and discarded.
Set AGENT_PRIVATE_KEY in the process environment when launching the server. Used as a fallback when the tool call does not provide one.
!Treat the signing key like you would treat it anywhere else
Routey does not persist the key, log the key, or transmit it past the viem wallet client that signs the single transaction. That does not make it safe to hand an untrusted agent a hot-wallet key to your main account — create a dedicated funding wallet for agent use, load it with the budget you are willing to lose, and rotate if a session ends badly.
Tool reference
get_quote
Fetches quotes from both providers and returns both plus the winner. Read-only, no signing. Typically returns in 300–500 ms depending on RPC latency.
Input
Field
Type
Required
Notes
tokenIn
string
yes
Symbol, e.g. "ETH", "USDC".
tokenOut
string
yes
Symbol.
amount
string
yes
Human units (e.g. "0.5"). Decimals are applied server-side.
chain
string
no
Defaults to "ethereum". One of ethereum | base | arbitrum | optimism.
Example call
agent prompt
Use routey:get_quote to quote 0.5 ETH → USDC on ethereum.
Executes a swap through the chosen (or best) provider and returns the transaction hash once broadcast. Signs with the private key as described in MCP server → Signing key.
Input
Field
Type
Required
Notes
tokenIn
string
yes
Symbol.
tokenOut
string
yes
Symbol.
amount
string
yes
Human units.
chain
string
no
Default "ethereum".
slippage
number
no
Percent. Default 0.5.
provider
enum
no
"uniswap" | "relay" | "best". Default "best" (re-quotes and picks the winner at execution time).
No arguments. Returns the four chains with their IDs, names, and slugs, so an agent can enumerate routes before asking a user which network to execute on.
Base URL is the origin the Next.js app is served from — in development that is http://localhost:3000. There is no authentication; if you want to lock it down, put the server behind your own reverse proxy or add middleware.
GET /api/quote
Fetches both provider quotes and returns the winner.
400 MISSING_FIELDS — one of the four required fields is missing.
400 UNKNOWN_CHAIN — unknown chain slug.
500 SWAP_FAILED — executor error. The error field carries the underlying message.
GET /api/events (SSE)
Opens a server-sent event stream. Every swap lifecycle event, whether initiated from the web UI or the API, is multicast to every open connection.
Response is text/event-stream with data: {…json…} frames. A : heartbeat comment is sent every 25 seconds so idle connections survive intermediary timeouts.
The chain slug is not one of the four supported values.
INVALID_AMOUNT
400
Amount is missing, not a number, or ≤ 0.
INVALID_JSON
400
Request body failed to parse as JSON.
MISSING_FIELDS
400
One or more of the required fields (tokenIn, tokenOut, amount, chain) is missing.
SWAP_FAILED
500
The executor raised. Underlying message is in the error field.
End-to-end example
A self-contained TypeScript script: fetch a quote, check the savings, execute if the savings exceed a threshold, confirm from the event stream.
bot.ts
import type { QuoteResult, SwapResult, SSEEvent } from './lib/types'
const BASE = 'http://localhost:3000'
async function quote(tokenIn: string, tokenOut: string, amount: string, chain = 'ethereum') {
const url = new URL(`${BASE}/api/quote`)
url.searchParams.set('tokenIn', tokenIn)
url.searchParams.set('tokenOut', tokenOut)
url.searchParams.set('amount', amount)
url.searchParams.set('chain', chain)
const res = await fetch(url)
if (!res.ok) throw new Error(`quote failed: ${res.status}`)
return res.json() as Promise<QuoteResult>
}
async function swap(tokenIn: string, tokenOut: string, amount: string, chain = 'ethereum') {
const res = await fetch(`${BASE}/api/swap`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ tokenIn, tokenOut, amount, chain, slippage: 0.5, provider: 'best' }),
})
if (!res.ok) throw new Error(`swap failed: ${await res.text()}`)
return res.json() as Promise<SwapResult>
}
function watch(onEvent: (e: SSEEvent) => void) {
const es = new EventSource(`${BASE}/api/events`)
es.onmessage = (m) => { try { onEvent(JSON.parse(m.data)) } catch {} }
return () => es.close()
}
async function main() {
const stop = watch((e) => {
if (e.type === 'swap_confirmed') {
console.log('✓', e.payload.txHash)
}
})
const q = await quote('ETH', 'USDC', '0.5')
if (!q.best) { console.log('no route'); stop(); return }
const saved = parseFloat(q.savedAmount)
if (saved < 1) { console.log('savings below threshold, skipping'); stop(); return }
const tx = await swap('ETH', 'USDC', '0.5')
console.log('broadcast', tx.txHash, 'via', tx.provider)
}
main().catch(console.error)
✎One pipeline, two signing surfaces
This script talks to the same /api/swap endpoint the web UI calls when a human clicks Swap Now. The executor only sees a signed transaction — whether the signer came from a browser wallet or a private key in AGENT_PRIVATE_KEY is below its level of abstraction.