[ TUTORIAL ]
Connect your own agent
Oddysey exposes a remote MCP server so your agent can read what you approved, act on it wherever you actually trade, and report the fill back. This is the part that turns a review queue into a working loop.
What this gets you
Oddysey is the supervised half of an agent workflow. The executing half is yours, and it stays yours — Oddysey never learns your venue credentials and never touches your account.
The server speaks MCP over Streamable HTTP at /api/mcp, protocol version 2025-06-18, authenticated with a bearer token you issue in the deck. It exposes nine tools.
Set it up
- 1
Issue a deck token
In the deck, open Settings → MCP access and create a token with a label naming the client it is for. A token looks like
odsy_followed by 64 hex characters. - 2
Point your client at the server
Any MCP client that can send an
Authorizationheader will work. For clients that only speak stdio, bridge withmcp-remote:claude_desktop_config.jsonJSON { "mcpServers": { "oddysey": { "command": "npx", "args": [ "-y", "mcp-remote", "https://oddysey.dev/api/mcp", "--header", "Authorization: Bearer ${ODDYSEY_TOKEN}" ], "env": { "ODDYSEY_TOKEN": "odsy_..." } } } }In Claude Code, the same server can be added with
claude mcp add --transport http oddysey https://oddysey.dev/api/mcpplus an Authorization header. - 3
Shake hands
initializeHTTP POST /api/mcp authorization: Bearer odsy_... content-type: application/json { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "clientInfo": { "name": "my-trading-agent", "version": "1.0.0" } } }Send a client name you will recognise later: it is recorded against anything that client reports, which is how the ledger can distinguish two agents working the same deck.
- 4
Discover the tools
tools/listJSON {"jsonrpc":"2.0","id":2,"method":"tools/list"}Batched requests are answered as a batch, and notifications get
202with no body. The server never initiates messages, so aGETfor a server-sent stream is refused with405rather than left hanging open.
Tool reference
9 tools · all scoped to the token's owner
| TOOL | ARGUMENTS | WHAT IT DOES |
|---|---|---|
get_portfolio | — | Holdings at the latest quotes, plus market. Check market.live before reasoning from the prices. |
list_watches | — | Every watch on this deck. |
create_watch | symbol, thresholdPct, action, note? | Arms a new watch. Authorises nothing on its own. |
set_watch_status | watchId, status | Pause or resume. Returns changed:false if it was already in that state. |
list_proposals | status? | Filter by pending, approved, executed, rejected, or all. approved is the one your agent wants. |
get_proposal | proposalId | One proposal in full, including rationale and both allocations. |
list_ledger | limit? (1–200, default 50) | The audit trail, newest first. |
evaluate_watches | — | Runs the sweep now instead of waiting. Watches in cooldown are skipped. |
record_execution | proposalId, venue, orderRef, note? | Reports a fill and closes out an approved proposal. Refused for anything not approved. |
A sane agent loop
# a sane agent loop against Oddysey
1. get_portfolio
└─ if market.live is false, say so and stop reasoning from the prices
2. list_proposals { status: "approved" }
└─ nothing to do? exit. do not go looking for pending ones to act on.
3. for each approved proposal:
get_proposal { proposalId } # full rationale + allocations
… execute on your own rail …
record_execution { proposalId, venue, orderRef, note }
4. list_ledger { limit: 20 } # confirm what you just wrote landedThe server hands your client instructions at initialize that say much the same thing: this is a supervision layer, not an execution venue; approval is a human’s job; and when market.live is false you should say so rather than reason from placeholder prices.
What calls cost
The handshake is free and reads are a fraction of a cent. Nothing here is billed to a card — a deck spends a credit balance, and an agent that runs it dry can refill it itself.
per call · USDC, charged before the tool runs
| PRICE | CALLS | WHY |
|---|---|---|
Free | initialize, ping, tools/list | An agent should never pay to find out what it may do. |
$0.0002 | get_portfolio, list_watches, list_proposals, get_proposal, list_ledger | Polling for approved work is the behaviour we want, not the behaviour we tax. |
$0.002 | create_watch, set_watch_status, record_execution | These land in someone's audit trail. |
$0.005 | evaluate_watches | Fans out to a quote provider and a model, on Oddysey's side. |
An unknown tool name costs nothing — it comes back as METHOD_NOT_FOUND, and billing for a refusal would be indefensible. Every priced response carries x-oddysey-credits: the remaining balance in atomic USDC units, so your client can watch itself drain and top up before it stalls mid-loop.
When something returns 402
Out of credit, the endpoint quotes a price instead of failing. The body is an x402 challenge — the same shape any x402 client already knows how to satisfy.
HTTP/1.1 402 Payment Required
content-type: application/json
{
"x402Version": 1,
"error": "This request costs $0.0020 and your deck is out of credit. Pay $1.00 to continue.",
"accepts": [{
"scheme": "exact",
"network": "base-sepolia",
"maxAmountRequired": "1000000",
"resource": "https://oddysey.dev/api/mcp",
"payTo": "0x...",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"maxTimeoutSeconds": 120
}]
}
# retry the identical request with a signed payment
POST /api/mcp
authorization: Bearer odsy_...
x-payment: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3QiLC4uLn0=Sign the quoted amount as an EIP-3009 authorisation, base64 it into an x-payment header, and send the identical request again. Oddysey verifies and settles through a facilitator — it never holds a key for this and never sees yours — credits your deck, then runs the call. The settlement receipt comes back in x-payment-response.
Prefer to buy in bulk? POST /api/credits/topup with { "usd": 25 } and the same deck token takes one payment for a whole pack. And if a deployment has no payment address configured, metering is inert: no call is ever priced and no 402 is ever issued.
Token hygiene
- One token per client, labelled. Revoking then becomes surgical rather than an outage for every agent you run.
- Watch
last usedin the settings panel. A token being used while the client that owns it is idle is the signal you want to catch. - Revocation is immediate — the next request with that token gets
401. - A token grants exactly what the nine tools above do, on one operator’s data. It cannot approve, cannot spend, and cannot reach another deck.
When something returns 401
Three causes, in order: the header is missing or malformed (it must be Authorization: Bearer odsy_…), the token was revoked, or the token was never valid. The response carries a WWW-Authenticate challenge, and the server does not distinguish between an unknown token and a revoked one — that distinction would be useful to an attacker and to nobody else.