{
  "markdown": "# oddsrail\n\n<!-- mcp-name: app.oddsrail/polymarket-kalshi-trading -->\n\n**The rail AI agents use to trade prediction markets.**\n\nAn MCP server that gives any agent (Claude Code, Claude Desktop, or anything\nMCP-compatible) prediction-market access across **Polymarket and Kalshi**:\nmarket search, orderbooks, price history, positions, and order routing, with\n**on-chain builder-code attribution** on Polymarket, plus two premium signal\ntools (in-play overshoot/fade detection, resolution dispute-risk).\n\n**Free to use, and free of fees.** oddsrail ships with a project builder code\nregistered at **0 bps**, so orders routed through it are attributed without\nadding a single basis point to anyone's trade. The project's income is a share\nof Polymarket's weekly builder reward pool, paid by Polymarket's own program,\nnot by you. Running your own builder profile instead is one environment\nvariable (`ODDSRAIL_BUILDER_CODE`), and `server_info` always tells you which\ncode is in use. No fee tiers, no paywalled tools, no account required.\n\n## How oddsrail compares\n\nVerified against each alternative directly (their repos, live endpoints, and\nregistry entries, September 2026), not from their marketing:\n\n| | oddsrail | raw venue APIs | pmxt | Simmer | Polymarket agent-skills |\n|---|---|---|---|---|---|\n| 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 |\n| Venues you can trade | Polymarket, Kalshi | one each | Polymarket, Opinion, Limitless (hosted writes); a dozen more for data | Polymarket, Kalshi, plus its own $SIM sandbox markets | Polymarket only |\n| Custody | non-custodial; keys never leave your machine | yours | hosted mode: \"PMXT handles custody, signing infrastructure\"; self-hosted mode: your keys | self-custody, local signing | yours (documentation only) |\n| Attribution you control | yes: `ODDSRAIL_BUILDER_CODE` overrides the 0 bps default | n/a | not documented | not documented | documents builder headers for your own code |\n| Cost to the trader | 0 bps, free tools | free | hosted pricing not in the README | not documented | free |\n| Open source | MIT, full source | n/a | MIT, ~2.1k stars | not stated | docs; license not stated |\n| Operator guardrails | notional caps, open-order cap, allowed markets; enforced pre-request, in dry-run too | none | not documented | per-trade limits, daily caps, stop-loss/take-profit, kill switch | none |\n| Paper trading | dry-run fills against the live book, P&L | none | not documented | virtual $SIM sandbox, then graduate to real money | none |\n| Book-walked cost, settlement audit, jurisdiction-classified failures, dated venue-quirk notes | yes, all four | no | not documented | not documented | quirks partly documented |\n| Realtime | `watch_book`, bounded | websocket, yours to wire | not documented in the README | not documented | websocket documented |\n\nVerified 2026-09-02 from each project's own README or docs (pmxt: github.com/pmxt-dev/pmxt; Simmer: docs.simmer.markets; agent-skills: github.com/Polymarket/agent-skills). \"Not documented\" means exactly that, not \"absent\". Re-check before quoting; these projects move.\n\n**The wedge, in one line:** pmxt is the reference for trading *everywhere*; Simmer is the reference for an agent economy with a sandbox and a reputation layer; oddsrail is the reference for trading *correctly, non-custodially, with attribution you own*.\n\n**Where the others are honestly ahead:** pmxt trades three venues to our two and covers a dozen more for data, with hosted convenience and a community many times ours. Simmer has a virtual-balance sandbox, stop-loss and take-profit rails we do not have, a public reasoning/reputation layer, and a strategy-skills marketplace. Polymarket's agent-skills is the venue's own documentation and covers bridging and deposits, which oddsrail does not.\n\n¹ The raw Polymarket API *has* the endpoints. It also models rejections as\n`ok:false` return values, orders its books worst-first, ships a trades\nendpoint that returns the market's **public** tape, and enforces an\nundocumented $1 minimum notional. oddsrail exists because we hit every one of\nthose and encoded the fix.\n\n² Kalshi prices are dollar strings (integer cents were removed 2026-03), its\norderbook is bids-only on both sides, and its current SDK requires\nPython ≥3.13. All normalised here.\n\n## Quickstart\n\nPython 3.11+ required.\n\n```bash\npip install oddsrail\n```\n\n```bash\nclaude mcp add --transport stdio oddsrail -- oddsrail\n```\n\nOr from a clone, without installing:\n\n```bash\npython3 -m venv .venv && .venv/bin/pip install -r requirements.txt\n```\n\n```bash\nclaude mcp add --transport stdio oddsrail -- /abs/path/to/oddsrail/.venv/bin/python -m oddsrail.server\n```\n\nThen ask the agent: *\"search markets about the World Cup final and run the\novershoot signal on the favorite\"*.\n\n## Install in one step\n\n| Client | How |\n|---|---|\n| Claude web or desktop, nothing to install (hosted, paper trading) | Settings, Connectors, Add custom connector, URL `https://mcp.oddsrail.app/mcp`, then sign in with your email. See [Hosted](#hosted-nothing-to-install). |\n| Claude Code (hosted, paper trading) | `claude mcp add --transport http oddsrail https://mcp.oddsrail.app/mcp` |\n| Claude Code (plugin, with the four workflow skills) | `claude plugin marketplace add hmesutozsoy/oddsrail` then `claude plugin install oddsrail@oddsrail` |\n| Claude Code (server only) | `claude mcp add --transport stdio oddsrail -- uvx oddsrail` |\n| Any agent that reads skills | `npx skills add hmesutozsoy/oddsrail` |\n| Cursor | [Install oddsrail in Cursor](https://cursor.com/en/install-mcp?name=oddsrail&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyJvZGRzcmFpbCJdfQ==) |\n| VS Code | [Install oddsrail in VS Code](vscode:mcp/install?%7B%22name%22%3A%22oddsrail%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22oddsrail%22%5D%7D) |\n| Anything else that speaks MCP over stdio | `{\"command\": \"uvx\", \"args\": [\"oddsrail\"]}` (or `pip install oddsrail` and run `oddsrail`) |\n\nThe plugin and the one-click links launch the server with `uvx`, so they\nneed [uv](https://docs.astral.sh/uv/) on the machine. Without uv, `pip\ninstall oddsrail` gives you an `oddsrail` command to point any client at.\nEverything starts in dry-run.\n\nThe four skills (`skills/*/SKILL.md`) are generated from the server's own\nMCP prompts by `scripts/gen_skills.py`, and a test fails if they drift, so a\nskill and the prompt it mirrors can never disagree.\n\n## Hosted: nothing to install\n\n`mcp.oddsrail.app` runs the same server as a remote MCP endpoint with\naccounts, so an agent inside Claude can use it without a machine of its own.\nAdd the URL as a custom connector (Pro, Max, Team and Enterprise plans), sign\nin with your email when Claude asks, and every call from then on carries\nyour account.\n\nWhat the hosted server is, in one breath: Polymarket market data, the signal\ntools, `check_order`, and **paper trading with a $1,000 virtual bankroll per\naccount**, filled against the live book. What it is not: a place where money\nmoves. It holds no wallet keys, executes no real order, and serves no Kalshi\ntools (Kalshi's API Developer Agreement limits API use to a member's own\ntrading, so a shared service cannot route it). Account-scoped tools such as\n`open_orders` and the gasless relayer tools are absent, because on a shared\nserver they would describe nobody's account. Twenty-four tools remain: the\npublic-data and paper tools plus `arena_register`, `arena_unregister` and\n`arena_status`, which put the account's paper ledger on the public board.\n\nLive trading stays self-hosted: `pip install oddsrail` with your own key, and\nthe same `place_order` posts real orders when you set `ODDSRAIL_DRY_RUN=0`.\nThe paper ledger you build up in Claude is yours to reset with\n`paper_reset`; nothing else about the account exists. Privacy policy:\n[oddsrail.app/privacy](https://oddsrail.app/privacy). Source:\n`oddsrail/cloud/` and `oddsrail/hosted.py`; deployment notes in\n`deploy/cloud/`.\n\n## Builder page and arena\n\n[oddsrail.app/build](https://oddsrail.app/build) is a set of switches:\nstrategies (fade overshoots, buy near-certain resolutions, trade your own\nprobability, follow the move, two-sided quotes), risk rules (stop loss as a\nreview rule, take profit, daily loss limit, never add to losers, exposure\ncaps) and market hygiene (dispute risk, fill quality, watching the book).\nPress **Run** and the switches execute on the hosted server as a\ndeterministic paper pass (`oddsrail/cloud/runner.py`): the universe is\nscanned, each strategy's signal is computed with the same code the MCP\ntools use, every order goes through `check_order`, fills come from the live\nbook into a paper ledger that belongs to your browser, and the pass comes\nback as a list of decisions with verdicts and reasons. No account, no\ninstall, no key, no model. One pass is one tick at the current book, not a\nbacktest: the ledger carries positions and resting quotes between passes,\nand nothing is watched in between, so a record needs many passes. The same switches also compose into a prompt for\nrunning the pieces inside your own Claude with the connector; the\ntwo-sided-quotes piece carries the maintainer's own warning about markout\neither way.\n\n[oddsrail.app/arena](https://oddsrail.app/arena) is the public board. Paper\ndivision: hosted accounts that entered themselves with `arena_register`,\nranked by return on the virtual bankroll, served as JSON from\n`mcp.oddsrail.app/arena/paper.json`. Live division: wallets whose Polymarket\nfills carry the oddsrail builder code, ranked by attributed volume from the\npublic feed, with realized and open P&L from Polymarket's data API; register\nby pull request or the form on the page (`site/arena/agents.json`). No\nprizes yet; the rules are on the page.\n\n## See the footguns yourself, no keys\n\n```bash\npip install oddsrail && python examples/footguns.py\n```\n\nSix things the venue APIs get wrong, shown live against the public\nendpoints: the worst-first order book, Kalshi's bids-only dollar-string\nladders, geoblocks that look healthy until the order, the advisory geoblock\nverdict, the deprecated Gamma endpoint, and naive cross-venue matching. Each\nsection prints the raw trap, then the oddsrail call that returns the right\nanswer. About ten seconds, read-only.\n\n## How attribution works (CLOB V2, verified Aug 2026)\n\n1. Get your **builder code** (a bytes32) at polymarket.com → **Settings →\n   Builders**. Set your fee rates there: taker up to 100 bps, maker up to\n   50 bps, additive on top of platform fees, settled to your builder wallet.\n2. `export ODDSRAIL_BUILDER_CODE=0x...` where the server runs.\n3. Every order any agent routes through `place_order` has the code placed in\n   the V2 order struct's `builder` field **before signing**, so attribution is\n   on-chain, visible in every `OrderFilled` event on CTF Exchange V2.\n4. Verify with the `builder_stats` tool (public builder-trades endpoint +\n   leaderboard).\n\nIf you skip this, orders carry the bundled oddsrail builder code\n(`0xa576c5ce…`, registered at 0 bps maker / 0 bps taker), costing you nothing\nand funding the project. If you set your own, yours wins; the default is a\ndefault, not a lock-in.\n\nThe oddsrail builder profile is **Verified** in Polymarket's builder program\n(2026-09-02), and Polymarket's builder team confirmed builder-code attribution\nas the right pattern for a self-hosted, non-custodial tool: no keys ship with\nthe product, and the code is attached and signed by the operator's own wallet.\n\n## Environment variables\n\n| Variable | Default | Meaning |\n|---|---|---|\n| `ODDSRAIL_DRY_RUN` | `1` | `1` = orders are simulated and returned, never posted. Set `0` to trade. |\n| `ODDSRAIL_BUILDER_CODE` | project default | Your bytes32 builder code. Overrides the bundled project default so attribution (and any reward-pool share) accrues to you instead. |\n| `POLYMARKET_PRIVATE_KEY` | unset | Operator wallet key; required only for real trading. Never leaves this machine. |\n| `POLYMARKET_WALLET_ADDRESS` | unset | Proxy/deposit wallet address, if the account uses one. |\n| `POLYMARKET_RELAYER_API_KEY` | unset | Your own Relayer API key (polymarket.com → Settings → Relayer API keys), for gasless `split_position` / `merge_positions` / `redeem_positions`. |\n| `POLYMARKET_RELAYER_API_KEY_ADDRESS` | unset | The address the relayer key was issued for. Both halves are required; without them the gasless tools send nothing. |\n| `ODDSRAIL_MAX_ORDER_NOTIONAL` | unset | Guardrail: max USDC notional per order. Enforced before any request, in dry-run too. |\n| `ODDSRAIL_MAX_SESSION_NOTIONAL` | unset | Guardrail: max cumulative notional of live orders submitted by this server process. |\n| `ODDSRAIL_MAX_OPEN_ORDERS` | unset | Guardrail: max resting orders on the account (live; checked against the venue before placing). |\n| `ODDSRAIL_ALLOWED_MARKETS` | unset | Guardrail: comma-separated Polymarket token ids and/or Kalshi tickers the agent may trade. Anything else is refused. |\n| `ODDSRAIL_PAPER` | `1` | Paper-trade dry-run Polymarket orders against the live book. `0` disables. |\n| `ODDSRAIL_PAPER_LEDGER` | `~/.oddsrail/paper.json` | Where the paper ledger lives. One local JSON file. |\n| `ODDSRAIL_PAPER_BANKROLL` | `1000` | Starting paper cash in USDC. |\n\n## Status\n\n**Offline tests:** 143 tests covering the paths where a bug costs money: the\nKalshi yes/no→bid/ask translation, Kelly sizing, book walking, cross-venue\npairing, signal edge cases, the dry-run safety net, and jurisdiction-failure\nhandling (a geoblock must never read as an empty search result or a resting\norder), plus the hosted server end to end (dynamic client registration, PKCE,\nmagic-link sign-in, token rotation, one paper ledger per account) against a\nreal local process. They need no keys and no network:\n\n```bash\npip install -e \".[dev]\" && pytest\n```\n\nCI runs them on Python 3.11, 3.12 and 3.13 on every push and pull request.\n\n**Live venues:** every read tool and both dry-run order paths have been driven\nend-to-end against real Polymarket and Kalshi through a real MCP client\nsession. Two attributed Polymarket orders (a buy and a sell) have been placed\nand confirmed on-chain.\n\n**Not yet exercised live:** the Kalshi *order placement* path. Its request\nshape is unit-tested and its endpoint verified, but no order has been sent to\na real Kalshi account, because the author does not yet have a funded,\nverified one. This is untested, not untestable. Treat `kalshi_place_order` as\nunproven and start in dry-run.\n\n## Where this works\n\nTwo different things can stop oddsrail from trading, and they have opposite\nremedies. One is a venue restriction, enforced at the order. The other is a\nnetwork filter, which breaks the connection itself.\n\n**Polymarket restrictions.** Polymarket publishes its restricted-jurisdiction\nlist as an API reference: <https://docs.polymarket.com/api-reference/geoblock>.\nThere are three tiers. OFAC-sanctioned jurisdictions (Iran, Syria, Cuba,\nNorth Korea, and the Crimea, Donetsk and Luhansk regions of Ukraine) are\nblocked on both the frontend and the API, with no new orders *and* no closing\nof existing positions. A longer second tier is **close-only on both the\nfrontend and the API**: existing positions can be closed, new ones cannot be\nopened. It includes the United States, the United Kingdom, France, Germany,\nItaly, Poland, Slovakia, Belgium, Singapore, Australia, New Zealand, Brazil,\nRussia, Taiwan, Thailand and the Canadian provinces of Ontario, Quebec,\nBritish Columbia and Alberta. A third group, Ireland, Japan, Malta (sports\nonly) and the Netherlands, is close-only on Polymarket's frontend, with the\nAPI explicitly not restricted.\n\nNote the shape of that failure: it lands on the order, not the connection.\nPublic reads answer normally, so oddsrail will look like it is working right\nup until an order is rejected. Verified against Polymarket's documentation on\n2026-08-31; Polymarket updates the list without notice, so read the URL\nrather than this paragraph.\n\n**Kalshi restrictions.** Kalshi is a single CFTC-designated contract market\nand it does admit members outside the United States, but its Member\nAgreement §VI names a long list of Restricted Jurisdictions whose members may\nnot trade Event Contracts, among them Australia, Belgium, Canada, France,\nIreland, Italy, New Zealand, Poland, Portugal, Singapore, Switzerland, the\nUnited Kingdom, Hungary, India, the United Arab Emirates and mainland China.\nThe list is published in Kalshi's Exchange Notice of 22 June 2026\n(<https://kalshi-public-docs.s3.amazonaws.com/regulatory/notices/Kalshi%20Exchange%20Notice%20(Updated%20Member%20Agreement)%20(22%20June%202026).pdf>),\nand Kalshi reserves the right to change it. The same section is explicit that the\nrestriction applies only to *trading Event Contracts* and does not by itself\nbar membership or non-trading access, so oddsrail's Kalshi read tools stay\nusable even where its order tools do not.\n\n**The two lists overlap heavily.** Kalshi is not a general fallback for a\nPolymarket-restricted operator, and the difference runs in both directions.\nAmong the jurisdictions polymarket.com lists as close-only, Germany, Brazil,\nSlovakia and the United States are not on Kalshi's restricted list; Japan and\nthe Netherlands are restricted by neither API (only by Polymarket's\nfrontend). Check both lists for your own jurisdiction rather than assuming\nthe other venue is open. The US case has its own wrinkle.\n\n**The United States.** polymarket.com, the venue oddsrail talks to, is\nclose-only for the US. Polymarket separately operates Polymarket US\n(polymarket.us), run by QCX LLC as a CFTC-regulated Designated Contract\nMarket. **oddsrail does not support it.** It is a different API host, a\ndifferent authentication model (API-key headers rather than EIP-712 wallet\nsignatures), a different SDK and a different funding rail. A polymarket.us\naccount and its keys will not work with this server. Kalshi does not list the\nUS as restricted, so for a US operator Kalshi is the venue oddsrail can\nactually reach, with no builder-code attribution, since Kalshi's REST API\nhas no such field.\n\n**Network filters.** Separately from any venue rule, a national filter can\nblock the domains outright. Turkey does this: Polymarket does not restrict\nTurkey, and Turkey is not on Kalshi's list either, but Turkish ISPs block\npolymarket.com. That is a connectivity problem, not an eligibility one, and\nit looks different: DNS failures, TLS errors, resets, or an ISP interstitial\npage served where JSON was expected. oddsrail classifies both shapes and\ntells the calling agent which one it hit.\n\n**Eligibility is the operator's, not the tool's.** oddsrail is self-hosted\nand non-custodial, which is a real advantage and also means *you* hold the\naccount and *you* make the venue's representations; there is no intermediary\nmaking them for you. Polymarket's trading flow requires an attestation that\nyou are not a U.S. person, are not located in a restricted jurisdiction, and\nare not \"using a VPN or other measures to circumvent or attempt to\ncircumvent\" restrictions, and states that Polymarket reserves the right to\nput a non-compliant wallet in close-only mode. Kalshi's §VI is a\nrepresentation about where you are domiciled, organized and located, re-made\neach time you place an order. `server_info` reports Polymarket's geoblock\nverdict for this machine's IP, but a technical probe is not a compliance\ncheck: the terms bind on residence, citizenship and incorporation, not on\negress IP. Read the terms; if any of this matters to you, get your own legal\nadvice. Nothing here is legal advice.\n\nThe signal logic, the MCP layer and the whole test suite run fine offline\nregardless.\n\n## Guardrails: limits the agent cannot argue with\n\nAnyone handing keys to an agent wants three things first: a cap on one order,\na cap on a session, and a fence around which markets it may touch. All three\nare operator-set environment variables (table above), enforced *before* any\nrequest goes out, in dry-run as well as live, so the agent meets the fence in\nrehearsal. A refusal is a structured answer that names the rule, the limit\nand the request:\n\n```json\n{\"accepted\": false, \"blocked_by\": \"guardrail\", \"rule\": \"max_order_notional\",\n \"limit\": 25.0, \"requested\": 99.5, \"note\": \"refused by an operator-set guardrail ... Nothing was sent.\"}\n```\n\nThe session counter lives in the server process; restarting it resets the\nbudget, which is the operator's call. `server_info` reports the active limits\nand how much of the session budget is used.\n\n## Paper trading: dry-run with a memory\n\nBy default, every dry-run Polymarket order is filled against the **live**\norder book, walked within the limit price; whatever does not fill rests as a\npaper order and fills later if the market crosses it. `paper_positions`\nreports cash, positions at current marks, realized and unrealized P&L and the\nresting paper orders; `paper_reset` starts over. The ledger is one local JSON\nfile. Be clear about what this is: fills assume no queue position, no latency,\nno market impact and no fees, so paper results are an upper bound on the same\nstrategy live. Kalshi dry-run orders still return the intent only.\n\n## Realtime: watch the book move\n\n`watch_book(token_id, seconds, max_events)` subscribes to a token's realtime\nstream and returns the events that arrived (book snapshot, then price changes\nand trades), bounded to at most 60 seconds so an agent cannot hang a session\non a quiet market. Use it after `get_orderbook` when the decision depends on\nthe book *moving*, not just where it is.\n\nIf the stream fails with `CERTIFICATE_VERIFY_FAILED` while the REST tools\nwork, your Python has no CA bundle (common with python.org macOS installs).\noddsrail classifies that as `local_tls` and tells the agent the fix: run\n`Install Certificates.command` from the Python folder in /Applications, or\nset `SSL_CERT_FILE` to the path printed by `python -m certifi`.\n\n## Gasless position management (relayer)\n\nThree tools move collateral without paying gas, through Polymarket's relayer:\n`split_position` (USDC → a full YES+NO set), `merge_positions` (matching\nYES+NO → USDC, or `max`), and `redeem_positions` (a resolved market's winning\nshares → USDC). All three respect dry-run and return the relayer transaction\nid and hash plus the terminal outcome.\n\nThey use **your own** Relayer API key, created at polymarket.com → Settings →\nRelayer API keys and exported as `POLYMARKET_RELAYER_API_KEY` +\n`POLYMARKET_RELAYER_API_KEY_ADDRESS`. That is the pattern Polymarket's builder\nteam recommends for a self-hosted tool: no builder secret ships with oddsrail,\nand each operator authenticates the relayer as themselves. Relayer limits are\nper builder tier: 100 requests/day unverified, 10,000 verified. Without the\nkey the tools return a structured \"not configured\" answer and send nothing;\nthey never fall back to a gas-paying broadcast from the signer.\n\n**Exercised live (2026-09-02):** a 1 USDC split and the matching merge went\nthrough the relayer from this code, gasless, on the maintainer's test account\nwith its own Relayer API key. Relayer ids and Polygon transaction hashes are\nin [docs/live-proof.md](docs/live-proof.md). `redeem_positions` is still\nunproven live: it needs a resolved market with winning shares, which that\naccount has not held yet. `redeemable_positions` lists what the configured\nwallet could redeem or merge right now, and the `settle_resolved` prompt\nchains the two.\n\n## Kalshi (venue #2)\n\nKalshi is **bring-your-own-key and single-tenant by design**: the operator\nsupplies their own API key, trades their own account, and this server caches\nnothing. That is deliberate: Kalshi's Developer Agreement limits API use to a\nmember's own trading (§3), bars facilitating other members' trading (§3.2) and\nsublicensing the API (§3.7), and restricts storing/sharing API data (§3.1). A\nhosted multi-tenant Kalshi service would not be compliant; a self-hosted one is.\n\n**Attribution does not exist here.** Kalshi Builder Codes are a\nSolana/DFlow/Jupiter integration; there is no builder or affiliate field\nanywhere on the REST API, so Kalshi order flow cannot be attributed or\nmonetised the way Polymarket's can. Kalshi is in oddsrail for coverage and\nsignal reach, not for routing revenue.\n\nTwo shapes on this API are easy to get wrong, so oddsrail normalises both:\n\n- **Prices are dollar strings, not cents** (`\"0.5600\"`), sizes are fixed-point\n  strings (`\"10.00\"`); the legacy integer-cent fields were removed in March\n  2026. All arithmetic uses `Decimal`.\n- **The orderbook is bids-only on both sides.** `yes_dollars` and `no_dollars`\n  are both bid ladders, ascending, so the best bid is the *last* element, and\n  a NO bid at $0.99 *is* a YES ask at $0.01. `kalshi_get_orderbook` returns a\n  conventional best-first bid/ask view of the YES book plus the raw ladders.\n\nOrder placement speaks natural terms, `outcome` (yes/no), `action`\n(buy/sell), `price` = probability of that outcome, and translates to Kalshi's\nYES-book `bid`/`ask` internally (buy NO @ 0.25 becomes ask @ 0.75). That translation is\nexhaustively unit-tested (`tests/test_money_paths.py`), since it is the\nobvious place to ship an inverted-position bug.\n\nCredentials: `KALSHI_KEY_ID` plus `KALSHI_PRIVATE_KEY_PATH` (PKCS#8 PEM) or\n`KALSHI_PRIVATE_KEY`. Set `KALSHI_DEMO=1` to hit the demo environment. Read\ntools need no key at all.\n\n## Cross-venue tools\n\n- **`find_markets(query)`**: searches Polymarket *and* Kalshi in one call and\n  returns one normalised shape per market: `venue`, `market_id` (the id that\n  venue's order tool takes), `title`, yes/no price as probabilities in (0,1),\n  best bid/ask, spread, 24h volume, close time, and `trade_with` naming the\n  tool to call. Use this when you do not already know the venue.\n- **`quote_cost(venue, market_id, side, size)`**: what a size would *actually*\n  cost, by walking the book rather than reading the top level. Returns average\n  fill price, slippage vs best, notional, levels consumed, and whether the size\n  is fillable at all, plus Polymarket's per-market fee schedule where it\n  publishes one. Kalshi does not publish fees in its market payload, so they\n  are reported as unknown rather than estimated.\n- **`compare_venues(query)`**: candidate same-event listings across venues.\n  **Not an arbitrage scanner.** Matching an event across venues is an\n  unsolved entity-resolution problem: naive title overlap cheerfully pairs a\n  Brazilian election with a Ukrainian one and reports a 70-point \"gap\" that is\n  fiction. Two gates apply (title similarity ≥ 0.5 *and* close dates within a\n  week), so it usually returns nothing, which is the honest answer. A price\n  delta between candidates is reported as `yes_price_difference`, never as\n  profit.\n\n### Kalshi search\n\nKalshi has no text-search endpoint. oddsrail searches by **event** (the\nhuman-readable index, with `with_nested_markets`) rather than paging tens of\nthousands of machine-named markets, and matches on word boundaries, without\nthat, \"fed\" matches \"German Bundestag\" and a Fed-rate query returns German\nelection markets. Results carry `truncated`, because a bounded scan means an\nempty result is not proof a market does not exist.\n\n## Order lifecycle & discovery\n\n- `order_status(order_id)`: resting / partially_filled / filled / gone, with\n  size_matched. The answer an agent needs after place_order.\n- `my_fills()`, `my_positions()`: the operator's executions and holdings,\n  no address juggling. (Fills come from the Data API activity feed; the\n  SDK's list_account_trades returns the market's *public* tape and is not\n  used.)\n- `cancel_all_orders()`: kill switch, flattens every resting order at once.\n- `resolution_criteria(venue, market_id)` returns the full resolution contract:\n  what resolves YES, who resolves it, from which sources. Read it before\n  trusting a price.\n- `closing_soon(hours)`: markets closing within N hours on either venue,\n  where activity concentrates.\n\n## Workflow prompts\n\nMCP prompts show up in clients as ready-made workflows, and they encode the\n*order* of operations that keeps an agent out of trouble; the sequencing is\nthe expertise, which a flat tool list cannot convey.\n\n- `/find_fade_setup(query, bankroll)`: signal → book → cost → resolution →\n  size → dry-run, with the rejection criteria at each step\n- `/check_cross_venue_edge(query)`: candidates → settlement audit → cost on\n  both legs, and says plainly when the answer is \"no edge\"\n- `/daily_review`: positions, resting orders, fills, closing-soon, attribution\n\n## Risk & settlement\n\n- **`settlement_audit(polymarket_id, kalshi_ticker)`**: the check that decides\n  whether a cross-venue price difference is an edge or a mismatch. Compares\n  close times, resolution sources, UMA dispute status and market structure on\n  **live data with no pre-curated pair list**, returning `ok` / `caution` /\n  `block` with reasons, and listing the checks it did *not* perform.\n- **`check_order(venue, market_id, side, price, size, intent)`**: the last step\n  before `place_order`. Deterministic checks of the proposed order against the\n  operator's own words and the live market: does the market exist and accept\n  orders, do the intent's words match the market and the YES/NO side, is the\n  price sane against the book, is the size above Polymarket's $1 minimum and\n  inside the guardrails, is there liquidity within the limit, is a resolution\n  source named. Returns `ok` / `caution` / `block` with the evidence per check\n  and a one-line read-back. No second model judges anything; nothing is sent.\n- **`position_size(bankroll_usd, price, fair_value)`**: fractional-Kelly sizing,\n  capped, refusing negative-edge bets, returning its own assumptions.\n\n## Tools (41)\n\n- `search_markets`, `get_market`, `get_orderbook`, `price_history`,\n  `get_positions`: read-only, no keys\n- `overshoot_signal`, premium: fresh panic-jump detection + this market's\n  historical reversion tendency (ported from the polymarket-wc analyzer)\n- `dispute_risk`, premium: transparent 0–100 heuristic for contested\n  (UMA-dispute-prone) resolutions\n- `place_order`, `cancel_order`, `open_orders`: trading, dry-run by default.\n  `price` is a probability in (0,1), `size` is in SHARES, and the exchange\n  enforces a **$1 minimum notional** on marketable orders. Trading tools carry\n  `destructiveHint` annotations so clients can gate them.\n- `builder_stats`: attribution verification + public builder leaderboard\n- `find_markets`, `compare_venues`, `quote_cost`: cross-venue (above)\n- `server_info`: config status, per-venue\n\nKalshi: `kalshi_search_markets`, `kalshi_get_market`, `kalshi_get_orderbook`,\n`kalshi_get_trades`, `kalshi_balance`, `kalshi_positions`,\n`kalshi_open_orders`, `kalshi_place_order`, `kalshi_cancel_order`.\n\n## Stack notes\n\n- Official unified SDK `polymarket-client` (0.6.x): `AsyncPublicClient` for\n  data, `AsyncSecureClient.place_limit_order(..., builder_code=...)` for\n  attributed orders. The legacy `py-clob-client` is archived and cannot\n  attach builder codes. Do not use it.\n- MCP SDK 2.0: `MCPServer` from `mcp.server.mcpserver` (the old\n  `mcp.server.fastmcp.FastMCP` import is gone in 2.x).\n- Kalshi is on plain `httpx` + `cryptography`, not the official SDK:\n  `kalshi-python-sync` requires Python >=3.13 and re-releases weekly in\n  lockstep with the spec version. Auth is RSA-PSS(SHA256, salt=digest length)\n  over `str(unix_ms) + METHOD + path`, where the path includes `/trade-api/v2`\n  and excludes the query string. Base URL is now\n  `external-api.kalshi.com`.\n- x402 (planned): the official `x402` PyPI package (v2.20+) can wrap MCP\n  tools directly (`x402.mcp`, payment rides in tool-call `_meta`), but its\n  MCP helpers currently target mcp 1.x, so integrating means pinning\n  `mcp>=1.28,<2` or waiting for the 2.x-compatible release. Mainnet\n  settlement needs a facilitator (Coinbase CDP: 1,000 free settlements/mo,\n  then $0.001). Keep free tiers of both signals so registries can index the\n  server.\n\n## Who this is for\n\nPolymarket's public builder leaderboard shows what a single operator routing\ntheir own flow is worth. Pulled **2026-08-31** via this server's own\n`builder_stats` tool. Re-run it, the numbers move:\n\n| | weekly volume |\n|---|---|\n| #1 (traderline) | $7.70M |\n| median of top 25 | $533K |\n| entry to top 25 | $140K |\n\nThe instructive rows are the small ones: **MagicMarkets routes $901K/week with\na single active user**; Jupiter $515K with one; Sharkbetting $1.15M with two.\nThose are bot operators routing their own flow, which is exactly who this is\nbuilt for.\n\n## Roadmap\n\n1. ~~Live smoke test from an unblocked network~~: done 2026-08-23, all tools pass\n2. Register builder code (polymarket.com → Settings → Builders), set fees to\n   0 bps at launch, export `ODDSRAIL_BUILDER_CODE`; first attributed order on\n   a tiny size\n3. ~~Kalshi as venue #2~~: done 2026-08-23, 9 tools, verified live\n4. x402 paid wrapping for the two signals once the mcp-2.x conflict clears\n5. Registry listings: official MCP registry (`mcp-publisher`, PyPI\n   `mcp-name:` marker), Smithery (needs public streamable-HTTP + a free\n   tool for their scanner), Glama (`glama.json`)\n\n## Listing / distribution\n\n- **GitHub**: https://github.com/hmesutozsoy/oddsrail (public, MIT)\n- **Glama**: auto-crawls GitHub; `glama.json` in the repo root claims\n  maintainership.\n- **PyPI**: https://pypi.org/project/oddsrail/ (`pip install oddsrail`)\n- **Official MCP registry**: listed as `app.oddsrail/polymarket-kalshi-trading` (renamed from `…-arbitrage` in 0.10.1; the old name is deprecated)\n  (published 2026-08-30, status active). Re-publish after a version bump with\n  `mcp-publisher publish`; keep `server.json`'s version in step with\n  `pyproject.toml` or the registry rejects it.\n- **Smithery**: requires a public HTTPS streamable-HTTP endpoint, available\n  once oddsrail is hosted rather than run locally over stdio.\n",
  "bytes": 33988,
  "sha": "00c9f94c813a5d480407e7abb6d3a0fc9468d7eb41b9d3362618a5c24545f56a",
  "repo_slug": "hmesutozsoy/oddsrail",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_hmesutozsoy_oddsrail_polymarke_8fd6398b/readme"
}