Are you the author? Sign in to claim
MCP server for the Nexus Exchange API — read market data and place trades from an AI agent
An MCP server that exposes the Nexus Exchange API as tools an AI agent (Claude Desktop / Claude Code) can call to read market data and place trades.
It talks to the real, public exchange gateway. Market-data and demo tools work with zero configuration; account and trading tools use HMAC API credentials read from environment variables.
Most tools now target the direct-indexer /api/v1 surface served at the
host root (ENG-4740 — the indexer serves its REST API directly instead of via
the gateway REST proxy). The routes that have no /api/v1 equivalent stay on
the legacy /api/exchange gateway, which remains live dual-stack
(ENG-4751), so nothing breaks. See "Migration to /api/v1" below.
| Tool | Status | Endpoint (surface) |
|---|---|---|
list_markets | ✅ Live (public) | GET /api/v1/markets/summary |
list_market_specs | ✅ Live (public) | GET /markets (legacy) |
get_ticker | ✅ Live (public) | GET /api/v1/markets/{id}/ticker |
get_tickers | ✅ Live (public) | GET /api/v1/tickers |
get_orderbook | ✅ Live (public) | GET /api/v1/markets/{id}/orderbook |
get_mark_price | ✅ Live (public) | GET /api/v1/markets/{id}/mark-price |
get_market_status | ✅ Live (public) | GET /api/v1/markets/{id}/status |
get_trades | ✅ Live (public) | GET /api/v1/markets/{id}/trades |
get_candles | ✅ Live (public) | GET /api/v1/markets/{id}/candles |
get_funding_history | ✅ Live (public) | GET /api/v1/markets/{id}/funding |
get_funding_samples | ✅ Live (public) | GET /api/v1/markets/{id}/funding-samples |
get_market_risk_params | ✅ Live (public) | GET /markets/{id}/risk-params (legacy) |
get_stats | ✅ Live (public) | GET /api/v1/stats |
get_stats_history | ✅ Live (public) | GET /api/v1/stats/history |
get_demo_account | ✅ Live (public) | GET /demo/account (legacy) |
get_demo_positions | ✅ Live (public) | GET /demo/positions (legacy) |
get_demo_orders | ✅ Live (public) | GET /demo/orders (legacy) |
get_balance | ✅ Live (needs key + direct gateway) | GET /api/v1/account |
get_account_summary | ✅ Live (needs key + direct gateway) | GET /api/v1/account/summary |
get_equity_history | ✅ Live (needs key + direct gateway) | GET /api/v1/account/equity-history |
get_positions | ✅ Live (needs key + direct gateway) | GET /api/v1/positions |
get_closed_positions | ✅ Live (needs key + direct gateway) | GET /api/v1/positions/closed |
get_open_orders | ✅ Live (needs key + direct gateway) | GET /api/v1/orders |
get_order | ✅ Live (needs key + direct gateway) | GET /orders/{id} (legacy) |
get_order_history | ✅ Live (needs key + direct gateway) | GET /api/v1/orders/history |
get_fills | ✅ Live (needs key + direct gateway) | GET /api/v1/fills |
get_funding_payments | ✅ Live (needs key + direct gateway) | GET /funding (legacy) |
get_withdrawals | ✅ Live (needs key + direct gateway) | GET /withdrawals (legacy) |
list_deposits | ✅ Live (needs key + direct gateway) | GET /deposits (legacy) |
get_rate_limit_status | ✅ Live (needs key + direct gateway) | GET /api/v1/account/rate-limit |
get_adl_history | ✅ Live (needs key + direct gateway) | GET /account/{addr}/adl-history (legacy) |
get_market_adl_events | ✅ Live (needs key + direct gateway) | GET /markets/{id}/adl-events (legacy) |
place_order | ✅ Live (needs key + direct gateway) | POST /api/v1/orders |
place_orders_batch | ✅ Live (needs key + direct gateway) | POST /api/v1/orders/batch |
amend_order | ✅ Live (needs key + direct gateway) | PATCH /api/v1/orders/{id} |
preview_order | ✅ Live (needs key + direct gateway) | POST /api/v1/orders/preview |
cancel_order | ✅ Live (needs key + direct gateway) | DELETE /api/v1/orders[/{id}] |
deposit_collateral | ✅ Live (needs key + direct gateway) | POST /account/deposit (legacy) |
submit_deposit | ✅ Live (needs key + direct gateway) | POST /deposits (legacy) |
claim_credit | ✅ Live (needs key + direct gateway) | POST /api/v1/account/credit |
claim_faucet | ✅ Live (needs key + direct gateway) | POST /faucet (legacy) |
adjust_isolated_margin | ✅ Live (needs key + direct gateway) | POST /account/margin (legacy) |
list_agents | ✅ Live (needs key + direct gateway) | GET /agents (legacy) |
register_agent | ✅ Live (needs caller EIP-712 signature) | POST /agents/register (legacy) |
revoke_agent | ✅ Live (needs key + direct gateway) | DELETE /agents/{addr} (legacy) |
login | ✅ Live (needs caller EIP-191 signature) | POST /auth/login (legacy) |
list_api_keys | ✅ Live (needs session token) | GET /keys (legacy) |
create_api_key | ✅ Live (needs session token) | POST /keys (legacy) |
delete_api_key | ✅ Live (needs session token) | DELETE /keys/{key_id} (legacy) |
get_ws_token | ✅ Live (needs key + direct gateway) | POST /ws/token (legacy) |
get_ws_token_legacy | ✅ Live (needs key + direct gateway) | POST /ws-tokens (legacy) |
get_health | ✅ Live (public) | GET /health (legacy) |
get_readiness | ✅ Live (public) | GET /ready (legacy) |
get_service_status | ✅ Live (public) | GET /status (legacy) |
list_tiers | 🔒 Admin (opt-in, see below) | GET /admin/tiers (legacy) |
set_tier | 🔒 Admin (opt-in, see below) | PUT /admin/tiers (legacy) |
delete_tier | 🔒 Admin (opt-in, see below) | DELETE /admin/tiers/{addr} (legacy) |
get_deposit_target | 🚧 Pending — server-side endpoint not built yet | none yet |
get_deposit_target is wired into the agent flow but returns a clear
not_yet_available message rather than faking a result; it lights up when the
server-side capability ships.
/api/v1Per ENG-4740 the gateway REST proxy is being eliminated: each backend
service exposes its own REST API and the indexer serves the exchange surface
directly under /api/v1 at the host root. This server calls those routes for
the v0.7.1 operations it exposes as tools (see
API-surface coverage below).
https://exchange.nexus.xyz), not the
…/api/exchange gateway path. /api/v1/* resolves at the root; the
legacy-only routes append /api/exchange. A NEXUS_EXCHANGE_API_URL that
still ends in /api/exchange is accepted and normalized./api/v1/orders for
v1 routes, the bare route (/orders) for legacy ones.cancel_order requires market_id when cancelling a single order (the
v1 route marks it required); market_id is optional with cancel_all to
scope a mass-cancel to one market./api/v1 route): list_market_specs,
get_market_risk_params, get_order (v1 mounts only PATCH + DELETE on
/orders/{id}), get_withdrawals, list_deposits, get_funding_payments,
get_adl_history, get_market_adl_events, deposit_collateral,
submit_deposit, claim_faucet, adjust_isolated_margin, the agent /
api-key / admin-tier tools, get_ws_token*, get_health, get_readiness,
get_service_status, and the demo/* reads.The tool surface covers 53 of the 62 distinct operations in Exchange API
spec v0.7.1 (92 spec operations counting the /api/v1 aliases of the
legacy routes; each aliased pair is one tool).
The pin advanced from v0.6.2 to v0.7.1 as a pin-only bump (ENG-6038); no new tools were added, so the v0.7.1 additions are not yet exposed — tracked in ENG-6136:
GET / PUT /account/cancel-on-disconnect./api/v1/bridge Phase A — bridge assets, deposit addresses, and deposit
tracking (five operations).TrailingLimit order type — a new place-order request variant (a schema
addition on the already-mapped order endpoint, so it changes no route count).Of the remaining gap, the WebSocket upgrade endpoints GET /ws and
GET /stream are unmapped by design: a request/response MCP tool cannot hold a
streaming socket open, so the server instead mints the auth token
(get_ws_token / get_ws_token_legacy) the caller uses to connect to them
directly. Separately, v0.7.1 drops the standalone /health and /ready
liveness routes (only /status remains), so get_health / get_readiness now
call routes the pinned spec no longer documents — reconciliation is folded into
ENG-6136.
NEXUS_EXCHANGE_API_KEY / NEXUS_EXCHANGE_API_SECRET. See the
"Authentication" note below about the public proxy.login (EIP-191) and register_agent (EIP-712) carry
a wallet signature the caller produces externally; this server never holds a
wallet key and cannot sign for you.*_api_key tools authenticate with a Bearer session
token from login, set as NEXUS_EXCHANGE_SESSION_TOKEN.list_tiers / set_tier / delete_tier use the
operator admin secret and mutate other accounts' fee tiers. They are not
registered unless NEXUS_EXCHANGE_ENABLE_ADMIN_TOOLS=1 is set (and
NEXUS_EXCHANGE_ADMIN_SECRET provided). Never enable these on an untrusted
agent surface.Destructive tools (revoke_agent, delete_api_key, delete_tier, and
cancel_order's mass-cancel) require an explicit confirm: true /
cancel_all: true flag so a stray call can't do damage by accident.
npm install
npm run build
npm start # runs the stdio MCP server
npm start waits on stdio for an MCP client; it is meant to be launched by
Claude rather than run by hand. To verify it works end-to-end against the live
API without a client, use the smoke check:
npm run smoke # lists tools, calls list_markets against production
Expected output ends with list_markets OK -> N markets.
Copy .env.example and set as needed. Only trading/account tools need
credentials — never commit real secrets.
| Variable | Required | Purpose |
|---|---|---|
NEXUS_EXCHANGE_API_URL | No | API host root (serves /api/v1). Defaults to https://exchange.nexus.xyz. A legacy value ending in /api/exchange is accepted and normalized. |
NEXUS_EXCHANGE_API_KEY | For account/trade tools | HMAC API key id (x-api-key). |
NEXUS_EXCHANGE_API_SECRET | For account/trade tools | HMAC secret (hex). |
NEXUS_EXCHANGE_SESSION_TOKEN | For *_api_key tools | Bearer session token from login (POST /auth/login). |
NEXUS_EXCHANGE_ADMIN_SECRET | For admin tools | Operator admin secret (ADMIN_SECRET). Only with the flag below. |
NEXUS_EXCHANGE_ENABLE_ADMIN_TOOLS | No | Set to 1 to register the admin tier tools. Off by default. |
Currently targets Exchange API spec v0.7.1.
The pinned version lives in .api-version; the spec itself is
published by
nexus-xyz/nexus-exchange-api.
This repo does not vendor a copy — the drift CI job fetches the pinned release
to check for drift, and the scheduled api-version-sync workflow opens a PR when
a newer spec releases. The line above is bot-managed; everything around it is
human-owned.
Every upstream request also sends this pin as an X-Nexus-Api-Version: <tag>
header (e.g. X-Nexus-Api-Version: v0.7.1), alongside a normalized
User-Agent: nexus-exchange-mcp/<version>, so the exchange edge can attribute
and segment usage by client and by the contract version this server targets.
The header value is the server's own compiled-against tag — it is baked in at
build time (a test keeps it equal to .api-version), so it is
never taken from caller input.
Signed requests use the same canonical HMAC-SHA256 scheme the indexer verifies
(backend/services/indexer/src/auth.rs):
<timestamp>\n<METHOD>\n<path>\n<query>\n<sha256hex(body)>
signed with the hex-decoded secret and sent as x-signature alongside
x-api-key and x-timestamp.
For /api/v1 routes the signed path includes the prefix (e.g. /api/v1/orders);
for legacy gateway routes it is the bare path (e.g. /orders). The client signs
whatever path it sends, which is exactly what the indexer verifies over.
Important: the public production host still fronts authenticated requests with a
proxy that signs with the site's own frontend key, so per-caller HMAC headers
are not honored there — authenticated tools resolve to the site account, not
yours. To trade as a specific account, point NEXUS_EXCHANGE_API_URL at a
direct indexer gateway that verifies client HMAC (for example a local
http://localhost:9090 from the exchange docker-compose). Until then, use the
public get_demo_* tools to demo the account flow with no secrets.
Add this to your Claude Desktop config
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS),
adjusting the absolute path to this package's dist/index.js:
{
"mcpServers": {
"nexus-exchange": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/nexus-exchange-mcp/dist/index.js"],
"env": {
"NEXUS_EXCHANGE_API_URL": "https://exchange.nexus.xyz"
}
}
}
}
To enable trading, add NEXUS_EXCHANGE_API_KEY / NEXUS_EXCHANGE_API_SECRET
to the env block and set NEXUS_EXCHANGE_API_URL to a direct gateway.
nexus-exchange
appears in the tools list.list_markets /
get_ticker and reports the live BTC-USDX-PERP price.get_demo_account and get_demo_positions against the live exchange.The stdio server above runs locally and holds your API key on your machine. The hosted Streamable HTTP server is the remote front door: it lets a trader add Nexus as a remote MCP server without running any key-holding software locally.
npm run build
npm run start:http # listens on :8080, MCP endpoint at /mcp, probe at /healthz
Behind a TLS-terminating ingress this is the public endpoint
https://mcp.exchange.nexus.xyz/mcp. A client adds it with:
claude mcp add --transport http nexus https://mcp.exchange.nexus.xyz/mcp
It exposes the same tool surface as the stdio server — both transports
register the identical ToolDef[] from src/tools/ via
createServerForClient in src/server.ts, so the tools never drift. The
transport is the SDK's StreamableHTTPServerTransport in stateful mode (one
MCP session per mcp-session-id), which also serves the SSE fallback stream
for server→client messages. Hosted traffic keeps the same
nexus-exchange-mcp/<version> User-Agent as the stdio CLI but appends a
(http) comment (nexus-exchange-mcp/<version> (http)) so usage attributes
to the hosted MCP in the dashboard while still segmenting under one product and
version.
OAuth 2.1 is out of scope for this MVP (tracked under the hardening work, ENG-3598, and scoped-key minting, ENG-3486). Until that lands, the hosted server takes the caller's existing Exchange HMAC credential as request headers, captured once at session initialize and reused for the session:
hljs language-textX-Nexus-Api-Key: <hmac key id> X-Nexus-Api-Secret: <hmac secret, hex>These are deliberately not named
x-api-key/x-signature(the upstream gateway's own headers) to avoid confusion. With no credential headers a session still serves public market-data tools and falls back to any server-env credentials. Open question for review: header passthrough is the simplest defensible MVP, but the long-term answer is OAuth-minted scoped (trade-not-withdraw) keys so the caller never hands us a raw secret — see ENG-3598 / ENG-3486.
npm run format # prettier --write
npm run lint # eslint
npm run typecheck # tsc --noEmit
npm test # unit tests (HMAC scheme, arg mapping, schemas)
npm run test:coverage # unit tests + coverage (text/lcov/json-summary); CI emits the %
npm run smoke # live end-to-end check against the gateway
Dual-licensed under MIT or Apache-2.0, at your option — same as the other Nexus Exchange SDKs.
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows