Four packages. One key. Execution off by default.
The safest thing an agent key can do on day one is nothing.
Scopes decide which tools exist. The risk policy decides whether any of them may move money. Both are enforced on the server, so a harness that has been talked into calling a tool still cannot get past the gate.
Execution is off by default
A freshly minted key can read. It cannot trade. The risk policy carries an executionEnabled flag that the owner has to set to true, and every execution path asserts it before anything else — no flag, no order, regardless of scopes.
Caps apply even when you omit them
Any cap a policy leaves out falls back to a conservative default: $100 per order, $500 of notional per day, $100 per swap, 100bps of slippage and 10x leverage. Forgetting to configure a limit gives you the tight limit, not none.
Allowlists, not honour systems
Per-key venue allowlists, market allow and deny lists, swap chain allowlists and an IP allowlist are all enforced server-side before an order is built. A denied market returns a typed policy error, not a partial fill.
Algos reserve their full notional
A TWAP or a scaled ladder is policy-checked for its entire size up front, not slice by slice, so a long-running schedule cannot creep past the daily cap one fill at a time.
What a cap you never set is worth.
| executionEnabled | false | Every execution route asserts it first. Off means read-only. |
| maxOrderUsd | $100 | Estimated notional of a single order. |
| dailyNotionalUsd | $500 | Rolling UTC-day notional across every venue. |
| swapMaxUsd | $100 | Per cross-chain swap. |
| maxSlippageBps | 100 bps | Ceiling on requested slippage. |
| maxLeverage | 10x | Rejected above this, before the order is built. |
Budgeted by class, not by endpoint.
| Reads | 600 / min | market:read and account:read calls. |
| Execution | 60 / min | Anything ending in :execute on the perp side. |
| Swaps | 30 / min | Quote, prepare, execute and status. |
Limits are per key and per minute, overridable per key. Every authenticated call is written to an audit log with the key id and the calling IP.
One key format. Two headers. No cookies.
A human owner mints keys for their agents and holds the only copy — the raw secret is returned exactly once, at mint or rotate, and only an irreversible hash is stored. Keys can be labelled, scoped, rotated, expired and revoked, and each one reports its own usage.
- Keys look like multi_sk_live_… or multi_sk_test_… — the environment is part of the key.
- Send Authorization: Bearer <key>, or x-multi-key: <key>. The SDK sends both.
- The hosted MCP endpoint reads the header per request, so one server serves many agents.
- An expired, revoked or IP-blocked key fails with a typed code, never a silent fallback.
1# Either header works. The key is issued once, at mint.2curl https://multi-venym-labs.vercel.app/api/agent/portfolio \3 -H "Authorization: Bearer multi_sk_live_..."4 5curl https://multi-venym-labs.vercel.app/api/agent/positions \6 -H "x-multi-key: multi_sk_live_..."7 8# The hosted MCP endpoint reads the same two headers, per request.9curl -X POST https://multi-venym-labs.vercel.app/mcp \10 -H "Authorization: Bearer multi_sk_live_..." \11 -H "Content-Type: application/json" \12 -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Six scopes. Three of them can move money.
Scope decides which tools a key can reach. The execution flag decides whether the three that move money are live. Both have to agree.
Markets, tickers, orderbooks, candles, recent trades, funding and open interest, and the routing recommendation itself. Public data stays public.
Positions, balances, open orders, running strategies, and the one-call cross-venue portfolio with exposure, PnL, funding and liquidation risk.
Smart-routed orders, batches of up to ten, cancels, open and close position with an optional bracket, TWAP schedules and scaled ladders.
Open and close a long/short pair as one position. Legs are placed with per-venue rate limiting and reported individually.
Quote a cross-chain route, prepare the unsigned transaction, and poll a swap's status. Nothing here moves funds.
The one call that broadcasts. Bounded by the swap cap, the slippage ceiling and the chain allowlist.
The typed client
@multidex/sdk
One class, eleven resources, full type coverage. Market data works with no key at all; everything else picks the key up from the environment.
- Resources for markets, trades, charts, routing, streams, orders, positions, portfolio, strategies, pairs and swaps.
- Reads MULTI_API_KEY, MULTI_BASE_URL and MULTI_STREAM_BASE_URL, so the same code runs locally and in a harness.
- Idempotent GETs retry twice on rate limits, 5xx and network errors; every request carries a 30-second timeout.
- Server-sent event streams for books, bars and trades, with an injectable fetch for runtimes that lack a global one.
1import { MultiClient } from "@multidex/sdk";2 3const multi = new MultiClient({ apiKey: process.env.MULTI_API_KEY });4 5// Public data — no key needed.6const book = await multi.markets.getOrderbook("BTC");7const route = await multi.routing.getRecommendation("BTC", { side: "BUY" });8 9// Execution — needs orders:execute AND executionEnabled on the key.10const order = await multi.orders.place({11 symbol: "BTC",12 side: "BUY",13 type: "MARKET",14 quantity: "0.01",15});16 17console.log(order.executedOn, route.recommended);The MCP server
multi-mcp
Two transports, one server. Run it locally over stdio, or point a harness at the hosted streamable-HTTP endpoint and let each caller present its own key.
- 29 registered tools: the 27 canonical ones plus two bounded watchers that collect a burst of live trades or book updates and return.
- Four prompts — analyse a market, review the portfolio, plan a position, plan a swap — and readable resources for the agent guide, markets and tickers.
- The HTTP transport is stateless: a fresh server per POST, with the key taken from Authorization or x-multi-key, so one deployment serves many agents.
- MULTI_READONLY=1 is a hard switch — execution tools are never registered, so the model cannot see a tool it is not allowed to call.
1{2 "mcpServers": {3 "multi": {4 "command": "npx",5 "args": ["-y", "@multidex/mcp"],6 "env": {7 "MULTI_API_KEY": "multi_sk_live_...",8 "MULTI_READONLY": "0"9 }10 }11 }12}The function-calling schemas
@multidex/tool-schemas
The same 27 tools as plain JSON Schema, plus the two adapters that shape them for OpenAI and for Anthropic. No client, no runtime, no opinion about your loop.
- toOpenAI() emits the chat-completions tools array; toAnthropic() emits the Messages API input_schema shape.
- Every tool declares the scope it needs, so a harness can filter the catalog before the model ever sees it.
- Prebuilt openai.tools.json and anthropic.tools.json ship as subpath exports for pipelines that would rather read a file than run code.
- Zero dependencies — it is the contract, not an implementation of it.
1import { TOOLS, toOpenAI, toAnthropic } from "@multidex/tool-schemas";2 3// Hand the model only what this key is allowed to do.4const readOnly = TOOLS.filter((t) => !t.scope.endsWith(":execute"));5 6const openaiTools = toOpenAI(readOnly);7const anthropicTools = toAnthropic(readOnly);The toolkit and the CLI
@multidex/agent-kit
Schemas with executors already bound, and a CLI that writes the MCP server into whichever harness you actually use.
- createMultiToolkit() returns bound tools plus openaiTools(), anthropicTools(), execute() and handle() — the last returns a tool-result shape instead of throwing.
- readOnly drops every execute-scoped tool client-side; include and exclude narrow the catalog further.
- multi-agent init writes the server config for Claude Code, Claude Desktop, Codex CLI, OpenCode, Cursor, Windsurf, Hermes, OpenClaw and Gemini CLI — or prints the snippet for anything else.
- multi-agent doctor checks connectivity, key validity and scopes; tools and harnesses list the catalog and the config locations.
1import { createMultiToolkit } from "@multidex/agent-kit";2 3const kit = createMultiToolkit({4 apiKey: process.env.MULTI_API_KEY,5 readOnly: false,6});7 8// OpenAI-shaped9const tools = kit.openaiTools();10 11// Dispatch a call the model made — never throws, returns { isError, text }.12const result = await kit.handle("execute_order", {13 symbol: "ETH",14 side: "SELL",15 type: "MARKET",16 quantity: "0.5",17});Nine harnesses know where their config lives.
The CLI carries a registry of every supported harness — the exact file, whether it takes a JSON merge or a TOML append, and the follow-up steps to print afterwards. It merges into what is already there rather than overwriting it, and a dry run shows you the diff first.
1# Wire the MCP server into a harness, in one command.2npx @multidex/agent-kit init claude-code --key multi_sk_live_...3 4# Or point it at the hosted endpoint, read-only.5npx @multidex/agent-kit init opencode --remote --readonly6 7# Check the key before you trust it with size.8npx @multidex/agent-kit doctor --key multi_sk_live_...27 tools, and the scope each one costs.
The same catalog backs all four packages. The MCP server registers two more on top — bounded watchers that collect a burst of live trades or book updates and return, so a single tool call can answer a question about right now.
- get_markets
- get_ticker
- get_orderbook
- get_candles
- get_trades
- get_routing
- get_market_stats
- get_positions
- get_balances
- get_orders
- get_portfolio
- list_strategies
- get_strategy
- execute_order
- batch_orders
- cancel_order
- open_position
- close_position
- create_twap_order
- create_scaled_order
- cancel_strategy
- open_pair_position
- close_pair_position
- swap_quote
- swap_prepare
- get_swap_status
- swap_execute
Quotes and prepared transactions never move funds — only the execute, open, close and create calls do. The MCP server annotates each tool accordingly, so a harness that honours read-only hints can tell them apart without parsing names.
Give it a key that can read. Turn on execution when you trust it.
The hosted MCP endpoint is https://multi-venym-labs.vercel.app/mcp, the REST base is https://multi-venym-labs.vercel.app/api, and both read the same key.