MCP server for Polymarket and Kalshi
The rail AI agents use to trade prediction markets.
Live books, costed fills, settlement audits and non-custodial order routing in one open-source MCP server your agent can call. Free to run. 0 bps on every trade. Keys never leave your machine.
pip install oddsrail
claude mcp add --transport stdio oddsrail -- oddsrail
- status
- Verified Polymarket builder
- surface
- 39 tools, 4 workflow prompts
- cost
- 0 bps added to trades
- default
- dry-run, with a paper ledger
- license
- MIT, self-hosted
What a session looks like
A real exchange, captured on 2026-09-02. Tool results are abridged; the numbers are the venue's.
you find markets about bitcoin and quote 100 shares on the favourite agent find_markets("bitcoin") {"markets": [{"venue": "polymarket", "title": "Will the price of Bitcoin be above $72,000 on September 2?", "yes_price": 0.991, "best_bid": 0.988, "best_ask": 0.994, "spread": 0.006, "volume_24h": 196435, "market_id": "4430…2778"}, …]} agent quote_cost("polymarket", "4430…2778", "BUY", 100) {"cost": {"fillable": true, "filled_size": 100, "avg_price": 0.994, "notional": 99.4, "slippage_vs_best": 0.0, "levels_consumed": [{"price": 0.994, "size": 100}]}, "fees": {"fees_enabled": true, "fee_type": "crypto_fees_v2", "taker_only": true}} agent 100 YES on "Bitcoin above $72,000 on Sept 2" costs $99.40 at 0.994, all at one level, no slippage. It pays $100 if it resolves YES: 0.6% gross before the taker fee. place_order is in dry-run; say the word and I will read back the intent before anything is signed.
How it compares
Against the raw venue APIs and the three products that actually compete today. Every cell is verified from their own docs and dated in the README.
| Capability | oddsrail | raw venue APIs | pmxt | Simmer | Polymarket agent-skills |
|---|---|---|---|---|---|
| What it is | self-hosted MCP server | the venues themselves | unified API + SDK + MCP, "CCXT for prediction markets" | agent trading platform + SDK + MCP | markdown skill docs for agents |
| Venues you can trade | Polymarket, Kalshi | one each | Polymarket, Opinion, Limitless (hosted); a dozen more for data | Polymarket, Kalshi, own $SIM sandbox | Polymarket only |
| Custody | non-custodial, keys never leave your machine | yours | hosted: "PMXT handles custody, signing infrastructure"; self-hosted mode uses your keys | self-custody, local signing | yours (docs only) |
| Attribution you control | ODDSRAIL_BUILDER_CODE overrides the 0 bps default | n/a | not documented | not documented | documents builder headers for your own code |
| Cost to the trader | 0 bps, free tools | free | hosted pricing not in the README | not documented | free |
| Open source | MIT, full source | n/a | MIT, ~2.1k stars | not stated | docs; license not stated |
| Operator guardrails | notional caps, open-order cap, allowed markets; enforced in dry-run too | none | not documented | per-trade limits, daily caps, stop-loss/take-profit, kill switch | none |
| Paper trading | dry-run fills against the live book, P&L | none | not documented | virtual $SIM sandbox | none |
| Book-walked cost, settlement audit, jurisdiction-classified failures, dated quirk notes | all four | no | not documented | not documented | quirks partly documented |
| Realtime | watch_book, bounded | websocket, yours to wire | not documented in the README | not documented | websocket documented |
Built from the things the raw API gets wrong
Each of these was found by driving the venues live, then encoded so an agent never has to rediscover it.
The book arrives worst-first
Polymarket's raw orderbook lists bids ascending and asks descending, so
bids[0]is the worst level. Every model's prior says index 0 is best. oddsrail normalises both venues to best-first and walks the book to quote what a size actually costs.Rejections don't raise
The exchange models a rejected order as a return value, not an exception. Reported carelessly, a rejection reads to an agent as a resting order. oddsrail branches on it:
accepted: false, nothing attributed, and an unrecognised response shape is never reported as posted.Geoblocks look healthy until the order
Restricted jurisdictions are enforced at order placement. Public reads answer normally, so everything works right up to the trade. oddsrail classifies every failure shape (jurisdiction, network filter, ISP interstitial, a missing local CA bundle) and gives the agent an actionable hint instead of a retry loop.
"Account trades" returns everyone's
The SDK call that sounds like your fills returns the market's public tape. oddsrail reads the operator's activity feed by wallet, with transaction hashes.
Kalshi has no yes/no side
Everything is quoted from the YES book as bid and ask; prices are dollar strings, not cents. Getting the translation backwards silently takes the opposite position. oddsrail lets the agent say
outcome=no, action=buyand translates it, tested exhaustively rather than by example.A price gap is not an edge
Identical wording on two venues does not mean identical settlement. oddsrail pairs candidates by similarity and close date, then audits resolution sources, UMA status and structure before anyone calls it arbitrage.
What's inside
30 read-only tools that need no keys, 9 trading tools that respect dry-run and the operator's guardrails, and four prompts that encode the order of operations that keeps an agent out of trouble.
Market data
- search_markets
- get_market
- get_orderbook
- watch_book
- price_history
- closing_soon
- kalshi_*
Cross-venue
- find_markets
- compare_venues
- settlement_audit
- resolution_criteria
Cost and sizing
- quote_cost
- position_size
- overshoot_signal
- dispute_risk
Trading
- place_order
- cancel_order
- cancel_all_orders
- kalshi_place_order
- split_position
- merge_positions
- redeem_positions
Lifecycle
- order_status
- open_orders
- my_fills
- my_positions
- redeemable_positions
- paper_positions
- server_info
Prompts
- find_fade_setup
- check_cross_venue_edge
- settle_resolved
- daily_review
Where the builder code goes
Nothing is proxied and nothing is stored. The only thing oddsrail adds to your order is a public attribution tag, signed by your key, on your machine.
- 1. your agentAny MCP client
- Claude Code, Desktop, Cursor and others
- speaks plain intent: "buy 100 YES at 0.62"
- 2. oddsrail, on your machineChecks, then signs
- guardrails, then the dry-run gate: simulate unless
ODDSRAIL_DRY_RUN=0 - book-walked cost, jurisdiction preflight
- builder code placed in the order struct, then signed with your key
- guardrails, then the dry-run gate: simulate unless
- 3. venuePolymarket CLOB, Kalshi
- your own credentials
- rejections branched, never reported as resting
- 0 bps builder fee added
- 4. on-chainCTF Exchange V2
- every
OrderFilledevent carries the code - attribution is public and auditable
- weekly pool share paid by Polymarket
- every
How it pays for itself without charging you
- custody
- None. You run the server; your keys sign your orders on your machine. Nothing is proxied and nothing is stored.
- fees
- 0 bps. Orders carry a builder code registered at 0 bps maker and 0 bps taker. No fee is added to any trade.
- attribution
- The code is placed in the CLOB V2 order struct before signing, so attribution is on-chain in every
OrderFilledevent. The project earns only a share of Polymarket's weekly builder pool, which Polymarket pays out of its own program. - override
- Set
ODDSRAIL_BUILDER_CODEto your own code and the share accrues to you instead. The default is a default, not a lock-in. - status
- Verified in Polymarket's builder program, with the attribution pattern for self-hosted, non-custodial tools confirmed by their builder team.
- guardrails
ODDSRAIL_MAX_ORDER_NOTIONAL,ODDSRAIL_MAX_SESSION_NOTIONAL,ODDSRAIL_MAX_OPEN_ORDERS,ODDSRAIL_ALLOWED_MARKETS. Set by you, enforced before any request, in dry-run too. The agent cannot change them.
# the whole configuration export ODDSRAIL_DRY_RUN=1 # default: simulate, papered export ODDSRAIL_BUILDER_CODE=0x… # optional: yours export ODDSRAIL_MAX_ORDER_NOTIONAL=25 export POLYMARKET_PRIVATE_KEY=… # only to trade export POLYMARKET_RELAYER_API_KEY=… # only for gasless ops export KALSHI_KEY_ID=… # only for Kalshi private # then, in any MCP client "find markets about the world cup and quote the cost of 100 shares on the favourite"
Both venues restrict trading by jurisdiction; reads work from restricted places. server_info reports this machine's geoblock verdict and venue reachability before your first order. Where this works. Kalshi's REST API has no attribution field, so Kalshi flow is never attributed or monetised.
Live from the builder program
Fetched in your browser from Polymarket's public data API. Nothing here is a screenshot. Reward epochs run Sunday 00:00 to Saturday 23:59 UTC.
Top 10 builders by attributed volume, this week
View as table
| # | Builder | Volume | Active users | Verified |
|---|
oddsrail, this week
…
Attributed volume is the sum of matched orders carrying the oddsrail code. Verified builders share a weekly USDC pool pro-rata to volume. There is no top-N cutoff, only a ranking. The attribution ledger shows which wallets that volume comes from, with the maintainer's own bot subtracted.