Docs / Connect your own agent

[ 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. 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. 2

    Point your client at the server

    Any MCP client that can send an Authorization header will work. For clients that only speak stdio, bridge with mcp-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/mcp plus an Authorization header.

  3. 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. 4

    Discover the tools

    tools/listJSON
    {"jsonrpc":"2.0","id":2,"method":"tools/list"}

    Batched requests are answered as a batch, and notifications get 202 with no body. The server never initiates messages, so a GET for a server-sent stream is refused with 405rather than left hanging open.

Tool reference

9 tools · all scoped to the token's owner

TOOLARGUMENTSWHAT IT DOES
get_portfolioHoldings at the latest quotes, plus market. Check market.live before reasoning from the prices.
list_watchesEvery watch on this deck.
create_watchsymbol, thresholdPct, action, note?Arms a new watch. Authorises nothing on its own.
set_watch_statuswatchId, statusPause or resume. Returns changed:false if it was already in that state.
list_proposalsstatus?Filter by pending, approved, executed, rejected, or all. approved is the one your agent wants.
get_proposalproposalIdOne proposal in full, including rationale and both allocations.
list_ledgerlimit? (1–200, default 50)The audit trail, newest first.
evaluate_watchesRuns the sweep now instead of waiting. Watches in cooldown are skipped.
record_executionproposalId, venue, orderRef, note?Reports a fill and closes out an approved proposal. Refused for anything not approved.

A sane agent loop

pseudocode
# 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 landed

The 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

PRICECALLSWHY
Freeinitialize, ping, tools/listAn agent should never pay to find out what it may do.
$0.0002get_portfolio, list_watches, list_proposals, get_proposal, list_ledgerPolling for approved work is the behaviour we want, not the behaviour we tax.
$0.002create_watch, set_watch_status, record_executionThese land in someone's audit trail.
$0.005evaluate_watchesFans 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.

402 → pay → retryHTTP
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 used in 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.