Skip to main content
Glama
ByBastianRok

polymarket-mcp-server

by ByBastianRok
README.md
# 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

A3.9/5.0

Scored across 23 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count3/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues