# oddsrail

> An MCP server that gives AI trading agents access to prediction markets
> across Polymarket and Kalshi, with on-chain builder-code attribution on
> routed Polymarket orders and premium signal tools.

Self-hosted and non-custodial: the operator runs the server themselves and
private keys never leave their machine. Orders are dry-run by default.

## What an agent can do with it

- `search_markets(query, limit)` — find markets by text; returns token ids,
  best bid/ask, spread, 24h volume, liquidity, end date, resolution status
- `get_market(id_or_slug)` — one market's details
- `get_orderbook(token_id)` — live bids/asks
- `price_history(token_id, hours, fidelity_minutes)` — recent price series
- `get_positions(address)` — a wallet's current positions
- `overshoot_signal(token_id, hours, threshold)` — detects fresh price jumps
  (panic moves) and reports whether a fade setup is active, plus that
  market's historical reversion tendency
- `dispute_risk(id_or_slug)` — 0-100 score for how likely a market's
  resolution gets contested (UMA dispute risk), with transparent reasons
- `place_order(token_id, side, price, size)` — limit order; price is an
  implied probability in (0,1); dry-run unless the operator enables trading
- `cancel_order(order_id)`, `open_orders()`
- `builder_stats(time_period)` — public builder leaderboard and this
  operator's attributed trades

## Cross-venue tools (prefer these when the venue is not already decided)

- `find_markets(query, limit, venues)` — searches both venues, one normalised
  shape: venue, market_id, title, yes_price/no_price as probabilities in
  (0,1), best_bid/best_ask, spread, volume_24h, close_time, trade_with
- `quote_cost(venue, market_id, side, size)` — true cost of a size by walking
  the book: avg_price, slippage_vs_best, notional, levels_consumed, fillable.
  Call this before sizing any trade.
- `compare_venues(query)` — candidate same-event listings across venues. NOT
  arbitrage: a price difference is not profit, matches are fuzzy, and it
  usually returns nothing. Verify resolution criteria by hand.

## Kalshi tools

- `kalshi_search_markets(query, limit, min_volume)`, `kalshi_get_market(ticker)`,
  `kalshi_get_orderbook(ticker, depth)`, `kalshi_get_trades(ticker, limit)`
- `kalshi_balance()`, `kalshi_positions()`, `kalshi_open_orders()`
- `kalshi_place_order(ticker, outcome, action, price, count)` — outcome yes|no,
  action buy|sell, price is the probability of THAT outcome in (0,1)
- `kalshi_cancel_order(order_id)`

Kalshi notes: prices are dollar strings not cents; the raw orderbook is
bids-only on both sides (a NO bid at 0.99 is a YES ask at 0.01) but
`kalshi_get_orderbook` returns a normal best-first bid/ask view. Kalshi needs
the operator's own API key for private endpoints; read tools do not.

## Install

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
claude mcp add --transport stdio oddsrail -- /abs/path/oddsrail/.venv/bin/python -m oddsrail.server

## Notes for agents

- Prices are implied probabilities in (0,1), not cents.
- `place_order` returns `{"dry_run": true, ...}` unless the operator has set
  ODDSRAIL_DRY_RUN=0 — check this before assuming an order was placed.
- Geography affects trading, not reading. Polymarket restricts order
  placement by jurisdiction (full list:
  https://docs.polymarket.com/api-reference/geoblock — among them the US,
  UK, France, Germany, Italy, Poland, Singapore, Australia, Brazil and four
  Canadian provinces are close-only, meaning positions can be closed but not
  opened; the OFAC tier, including Iran, Syria, Cuba and North Korea, is
  blocked entirely). Reads work from
  restricted places, so the first failure is usually a rejected order, not a
  connection error. Kalshi's Member Agreement §VI restricts a heavily
  overlapping set of countries from trading Event Contracts while expressly
  permitting non-trading access. Separately, some national filters block the
  Polymarket domains at the network level (Turkey does). Call server_info for
  this machine's Polymarket geoblock verdict; it is advisory, not a
  compliance check. As of 2026-08-31.
