polymarket-mcp-server
# polymarket-mcp-server
A **read-only** [MCP](https://modelcontextprotocol.io) server for [Polymarket](https://polymarket.com). It lets an MCP client (Claude Code, Claude Desktop, …) query markets, order books, price history, wallet positions, and market holders — using Polymarket's three public, unauthenticated APIs.
**No trading. No private keys. No signer. No authenticated CLOB endpoints.** It only uses the SDK's public read-only client; `viem`/`ethers` are not installed (note: `@polymarket/client` depends on `ox`, so `npm install` pulls some crypto primitives transitively, unused here). The market/data tools are `readOnlyHint: true`; the paper-trading + calibration tools only write a local, gitignored `data/` ledger of **simulated** bets — never real money, no order is ever placed.
---
## What it can do — 23 tools
**13 read-only** market/data tools, **5 simulated** paper-trading tools (local ledger, no real money), and **5 analytics/automation** tools.
| Tool | API | Purpose |
|------|-----|---------|
| `polymarket_search_markets` | Gamma | Find markets by keyword, or list top markets by volume. |
| `polymarket_get_market` | Gamma | Full detail of one market by slug, incl. CLOB token ids per outcome. |
| `polymarket_get_events` | Gamma | Events grouping related markets (a whole tournament/election). |
| `polymarket_get_orderbook` | CLOB (SDK) | Live best bid/ask, spread, mid, depth — is an edge *executable*? |
| `polymarket_get_price_history` | CLOB (SDK) | Historical implied probability over a window, with summary stats. |
| `polymarket_get_quote` | CLOB (SDK) | Quick midpoint / buy / sell / spread / last-trade for one outcome. |
| `polymarket_get_trades` | Data | Recent trades — a market's tape, or a wallet's trade history. |
| `polymarket_get_open_interest` | Data | Open interest (capital at stake) for one or more markets. |
| `polymarket_get_leaderboard` | Data | Global trader ranking by realized P&L or volume. |
| `polymarket_get_positions` | Data | Open positions + P&L for a public wallet. |
| `polymarket_get_closed_positions` | Data | A wallet's resolved positions with realized P&L. |
| `polymarket_get_user_activity` | Data | A wallet's full activity feed (trades, splits, merges, …). |
| `polymarket_get_market_holders` | Data | Largest holders ("whales") of each outcome in a market. |
| `polymarket_paper_open` / `_status` / `_close` | local | **SIMULATED** paper trading — record bets to a local ledger (fills at ask + taker fee), mark-to-market, auto-score at resolution, Brier vs the market. Opening is capped by the available virtual bankroll. **No real money, no order placed.** |
| `polymarket_paper_delete` / `_reset` | local | Rewrite the simulated ledger: drop one bet, or wipe it and start a fresh virtual bankroll (`_reset` needs `confirm: true`). Destructive — resolved bets stop counting towards P&L, ROI and Brier. |
| `polymarket_find_arbitrage` | CLOB (SDK) | Scan for baskets priced under $1 (Yes+No, or a negRisk event) net of fees — the one edge a read-only bot can *detect*. Detection ≠ capture. |
| `polymarket_xray` | mixed | One-call deep look: detail + quote + whales + price history + trade tape. |
| `polymarket_calibration_snapshot` / `_report` | mixed | Snapshot markets now; when they resolve, score a reliability curve + Brier — is the market well-priced? |
| `polymarket_daily_digest` | mixed | A "morning briefing": paper status + arbitrage scan + top movers + calibration status. |
The CLOB read tools run on the official [`@polymarket/client`](https://github.com/Polymarket/ts-sdk) SDK (public read-only client — no keys, no wallet); Gamma and Data run on native `fetch`. The paper-trading and calibration tools write only a local, gitignored `data/` ledger — never real money.
---
## The identifier model (read this once)
Polymarket uses several ids and it's easy to get lost:
- A binary market has a human **`slug`**, a **`conditionId`**, and **two `clobTokenIds`** — one per outcome (Yes / No).
- The order book and price history key on the **`token_id`** (a ~77-digit number), *not* the slug.
Flow: `slug` → `polymarket_get_market` gives you the `clobTokenIds` → CLOB tools use a `token_id`.
For convenience, the CLOB tools (`get_orderbook`, `get_price_history`, `get_quote`) accept **either**:
- `token_id` directly (preferred if you have it), **or**
- `slug` + `outcome` (e.g. `"Yes"`) — the server resolves the token id for you via Gamma.
---
## Prerequisites
- **Node.js ≥ 24** (required by the official Polymarket SDK; Gamma/Data still use the built-in `fetch`). Check with `node --version`.
- Windows: `winget install OpenJS.NodeJS.LTS` or download from [nodejs.org](https://nodejs.org/en/download).
## Install & build
```bash
npm install
npm run build # compiles TypeScript to dist/
```
## Try it with the MCP Inspector
```bash
npm run inspector # builds, then opens the MCP Inspector against dist/index.js
```
Then call e.g. `polymarket_search_markets` with `{ "query": "bitcoin" }`.
## Register in Claude Code
This repo already ships a project-scoped [`.mcp.json`](.mcp.json) with a **relative** path, so it works on any machine once you've run `npm install && npm run build` from the project root:
```json
{
"mcpServers": {
"polymarket": {
"command": "node",
"args": ["dist/index.js"]
}
}
}
```
The relative `dist/index.js` resolves from the project root (where Claude Code is launched). If your client needs an absolute path, substitute the full path to `dist/index.js` on that machine. No secrets or environment variables are required — everything it touches is public.
### Install from a clone
```bash
git clone <your-repo-url>
cd polymarket-mcp
npm install
npm run build
```
Then reload MCP / restart Claude Code (project `.mcp.json` servers need a one-time approval in an interactive session). Requires Node ≥ 24 on the new machine.
---
## Usage examples
- "What Polymarket markets are there about the Fed?" → `polymarket_search_markets { query: "Fed" }`
- "What's the real spread on Argentina to win the World Cup?" → `polymarket_get_orderbook { slug: "will-argentina-win-the-2026-fifa-world-cup-245", outcome: "Yes" }`
- "How has that market moved this month?" → `polymarket_get_price_history { slug: "…", outcome: "Yes", interval: "1m" }`
- "How is wallet 0xabc… doing?" → `polymarket_get_positions { wallet: "0xabc…" }`
- "Who are the whales on the Yes side?" → `polymarket_get_market_holders { slug: "…" }`
- "Give me the full picture on this market" → `polymarket_xray { slug: "…", outcome: "Yes" }`
- "Any arbitrage in the top events?" → `polymarket_find_arbitrage { scan_top: 10 }`
- "Track a simulated bet" → `polymarket_paper_open { slug: "…", outcome: "No", size_usdc: 50, p_estimate: 0.6 }`, then `polymarket_paper_status`
- "Start the paper lab over" → `polymarket_paper_reset { confirm: true, bankroll: 500 }` (erases every simulated bet — there is no undo)
- "My morning briefing" → `polymarket_daily_digest`
---
## Scheduling the daily digest (Windows)
The server is a **local** stdio process, so a cloud routine can't reach it. To get the digest
unattended, use **Windows Task Scheduler** to run a headless Claude command with its "Start in" set to
the project root:
```bat
claude -p "Run polymarket_daily_digest and print it" --allowedTools "mcp__polymarket__polymarket_daily_digest" --output-format text >> "%USERPROFILE%\polymarket-digest.log"
```
Requires: the PC on/awake at the trigger time; Claude Code installed and authenticated for
non-interactive use; the task's **Start in** = the project folder (so `.mcp.json`'s relative
`dist/index.js` resolves); and the one tool **pre-allowed** (a scheduled run can't answer permission
prompts). Each run consumes Claude usage. (`/loop` also works, but only while a session stays open;
cloud `/schedule` does **not** reach a local stdio server.)
---
## Design notes
- **APIs / transport:** Gamma `gamma-api.polymarket.com` and Data `data-api.polymarket.com` over native `fetch`; the CLOB reads (order book, price history, quote) go through the official `@polymarket/client` public client (`createPublicClient()`, no keys). See [`src/clobSdk.ts`](src/clobSdk.ts).
- **Rate limits:** Gamma allows ~60 req/min unauthenticated. The client backs off exponentially (honoring `Retry-After`) on 429/5xx and retries transient network errors.
- **Context discipline:** responses default to concise Markdown; pass `response_format: "json"` for full structured data. Every tool also returns machine-readable `structuredContent`.
- **JSON-string fields:** Gamma returns `outcomes`, `outcomePrices`, and `clobTokenIds` as JSON-*encoded strings*; the client parses and zips them so each outcome is paired with its token id and implied price.
## Project layout
```
src/
index.ts # entry: McpServer + stdio transport
constants.ts # base URLs, limits
types.ts # raw API + normalized types
client.ts # PolymarketClient: Gamma/Data fetch+backoff, error mapping, slug→token; CLOB via SDK
clobSdk.ts # official @polymarket/client public client + pure response mappers (CLOB reads)
arbMath.ts # pure arbitrage math (edge + depth-walked executable size)
calibrationMath.ts # pure calibration math (buckets, reliability curve, Brier, ECE)
paperFees.ts # taker-fee model + fractional-Kelly + Brier (pure)
paperStore.ts / calibrationStore.ts / dataDir.ts # local JSON ledgers (gitignored data/)
schemas.ts # Zod input/output schemas
format.ts # formatting + result/error helpers
tools/ # one file per tool (23)
```
## Scope / non-goals
Read-only by design. Order placement, cancellation, approvals, or anything requiring a signature/API key is intentionally **out of scope** — the official SDK is used only via its public read-only client (`createPublicClient()`), which pulls no wallet/crypto libraries. Global trader rankings are available via `polymarket_get_leaderboard`; `polymarket_get_market_holders` covers per-market whale visibility.
## License
MIT
## Screenshots
<!-- TODO: add a screenshot or GIF of Claude using the tools -->
## How it was built
Most of the code was written by Claude Code. I set the scope, made the design decisions and tested the behaviour.
TDQS
Scored across 23 tools
Most tools target clearly distinct resources and actions (market vs event vs orderbook vs positions). Composites like polymarket_xray and polymarket_daily_digest bundle existing tools but are explicitly described as shortcuts. Slight potential confusion between polymarket_get_quote and polymarket_get_orderbook, but descriptions clarify.
All tools share the polymarket_ prefix and snake_case formatting. Most follow a verb_noun pattern (search_markets, get_market, find_arbitrage), but there are mixed verb styles (get_ vs search_ vs paper_ vs xray) and a few noun-first names like calibration_snapshot.
At 23 tools, the set is above the typical 3–15 sweet spot and feels heavy for a read-only/paper-trading server. While each tool has a plausible role, several could be consolidated (e.g. paper status/close/delete/reset, calibration snapshot/report) without losing core functionality.
The surface covers market discovery, detailed data, user analytics, paper trading, arbitrage detection, calibration, and a daily digest. Missing real order execution, order cancellation, and real-time streaming, but those appear intentionally out of scope given the explicit no-real-money design.