Work / 02 / predmarket-mcp
Live prediction markets, as tools an agent can use.
Kalshi and Polymarket price thousands of real-world questions, from Fed decisions to recessions, but their APIs differ in almost every way. This open-source MCP server hides that behind six clean tools, so Claude or any agent can search, quote and compare both venues in plain language.
The idea
An agent asked “what are the odds of a US recession in 2027?” should answer with real prices, not a guess. Prediction markets have those prices, but an agent can't use them without learning two very different APIs. The server gives it six tools and one vocabulary: every price is a YES probability between 0 and 1.
| Tool | What the agent gets |
|---|---|
search_markets | Open markets for a query, alternating venues so neither crowds out the other |
get_market | Bid, ask, mid, last, volume and close time for one market |
get_orderbook | YES bids and asks, best first, with the spread |
get_recent_trades | Latest fills, all as YES prices |
get_price_history | Hourly or daily points with a first, last, min and max summary |
compare_venues | Markets that ask the same question on both venues, ranked by price gap |
Four problems worth solving
01 Two venues, one language
Kalshi quotes dollars as strings and keeps separate YES and NO books; Polymarket uses token IDs per outcome and JSON-encoded arrays. A Kalshi NO bid at p becomes a YES ask at 1 − p, and a Polymarket fill on the NO token is converted the same way. Volume keeps its native unit (contracts or USD) and is always labeled, never mixed.
02 Search where the API has none
Kalshi's documented API has no keyword search. The first version paged through every open event (50+ requests) into an inverted index: a cold search took about 10 seconds and could trip the rate limit. The server now uses the public search endpoint behind kalshi.com's own search box, one request in about 0.3 seconds, and falls back to the index automatically if that undocumented endpoint ever changes.
03 Built for agent traffic
Agents call tools in bursts and often repeat themselves. Requests are spaced evenly per venue instead of burst-then-backoff; 429s and 5xx retry with backoff and honor Retry-After; and a TTL cache collapses identical in-flight requests into one upstream call. A 15-topic comparison sweep makes 30 upstream requests with zero rate-limit errors.
04 Matching that doesn't fall for look-alikes
Two markets can share most of their words and still be different bets. The first version paired a Kalshi “Fed funds above 3.50%” market with a Polymarket “3.0%” market and reported a 98-point gap, which would badly mislead an agent. Now every pair must pass twelve checks built from real title pairs, each with a regression test. A few of them:
| Check | Look-alike it rejects |
|---|---|
| Same thresholds | “above 3.50%” vs “3.0%” |
| Same comparison | “above 3.75%” vs “exactly 3.75%” |
| Close vs touch | “Bitcoin above $150k on Dec 31” vs “Bitcoin reaches $150k in 2026” |
| Same direction | “cut by 25bps” vs “increase by 25 bps” |
| Same negation | “Fed cuts 1 time” vs “no Fed cuts” |
| Same date | “Ethereum above $2,200 on Nov 13” vs “… on November 9” |
| Same place | “GDP growth in 2026” (US) vs “UK annual GDP growth in 2026” |
| Same stage | “become the next Justice” vs “the next Supreme Court nominee” |
| Same scope | “cut rates before 2027” vs “emergency rate cut before 2027” |
| Shared action, not just names | “Who will dissent at the FOMC? (Powell)” vs “Powell out from the Fed Board” |
Pairs that pass are scored by word overlap weighted by rarity, so “Fed” and “2026” count less than “dissent” or “shutdown”. Close dates must be within 45 days (tighter for markets closing within two weeks), and pairs are matched one-to-one. Genuine pairs now line up closely, as they should:
# compare_venues(“fed hike”) · live, Nov 2026
gap -0.020 Kalshi exactly 1 Fed rate hike in 2026?
Poly 1 Fed rate hike in 2026?
gap -0.004 Kalshi exactly 3 Fed rate hikes in 2026?
Poly 3 Fed rate hikes in 2026?
gap 0.000 Kalshi hike by 25bps in December?
Poly increase by 25 bps in December?
The live board
To show the matcher working on real data, a GitHub Actions job runs the same code every six hours against both venues and publishes a static board: every matched pair as a dumbbell on one probability axis, seven days of history for each leg, and the look-alikes the matcher refused, with the reason. Most topics find no true pair on a given day, and the board says so rather than padding the list.
Quality
- Tests with mocked HTTP (respx) for parsing, unit conversion, order-book sorting, retries, caching, search fallback and matching regressions from real title pairs. No network needed.
- Tested end to end over the real MCP protocol: launch over stdio, list the tools, call them against live data.
- GitHub Actions CI on Python 3.12 and 3.13, with ruff lint and format checks.
- Every tool is marked read-only, and inputs are clamped to safe ranges.
Need agents connected to your data?
The same approach works for internal databases, CRMs, ticketing systems or any API: a small, well-tested MCP server that gives agents exactly the tools they need, safely.
