polymarket-book-mcp
# polymarket-book-mcp
An MCP server that reads Polymarket order books, market metadata, and price
history.
**No private key, no trading, read-only.** This package never imports
`py_clob_client`, `web3`, or `eth_account`, never reads a wallet key, and
never sends a POST or DELETE to any order endpoint. It only issues GET
requests to Polymarket's public Gamma and CLOB APIs, the same endpoints the
website itself uses to show you a book. There is no code path in here that
could place, modify, or cancel an order, because the client that would do
that was never written.
## Why another one
A survey of existing Polymarket MCP servers turned up two patterns: some
require a wallet private key to be configured just to read a public order
book, which is a strange amount of trust to ask for a read-only operation;
others expose market search or trade history but ship no order-book tool at
all, which is the one piece of data a market-making or quoting workflow
actually needs first. This package exists to fill that specific gap: order
books, cleanly parsed, with no signing key anywhere in the dependency tree.
## Install
```bash
uv venv
uv pip install -e .
```
## Usage with Claude Code / Claude Desktop
Point the config at the venv's Python interpreter directly:
```json
{"mcpServers": {"polymarket": {"type": "stdio",
"command": "/absolute/path/to/polymarket-book-mcp/.venv/bin/python",
"args": ["-m", "polymarket_book_mcp.server"]}}}
```
Or run it straight from GitHub with `uvx`, no local clone or install step:
```bash
uvx --from git+https://github.com/sunnywlad/polymarket-book-mcp polymarket-book-mcp
```
## Tools
| Tool | Parameters | Description |
|---|---|---|
| `search_markets` | `query`, `limit=10`, `include_closed=False` | Keyword search via Polymarket's real search backend. Results are interleaved across matching events, so a query like `bitcoin` returns several distinct events rather than one price ladder's worth of strikes. |
| `get_order_book` | `token_id`, `depth=10` | Live order book for one outcome token: best bid/ask, mid, spread, top N levels per side with cumulative size and notional. The central tool. |
| `get_market` | `url_or_slug` | Resolve a market URL or slug into its question, condition_id, per-outcome token ids, tick size, LP reward band, and status flags. |
| `get_quote` | `token_id` | Cheap price check: best_bid, best_ask, mid, spread only. |
| `get_price_history` | `token_id`, `interval="1d"`, `fidelity=60` | Historical price series for one outcome token, plus a min/max/first/last summary. |
## Data sources
Every network call this server makes, so you can verify it yourself:
- `GET https://gamma-api.polymarket.com/public-search` — market/event search (backs `search_markets`).
- `GET https://gamma-api.polymarket.com/markets?slug=...` — slug to conditionId (backs `get_market`).
- `GET https://clob.polymarket.com/book?token_id=...` — live order book (backs `get_order_book`, `get_quote`).
- `GET https://clob.polymarket.com/markets/{condition_id}` — market/token metadata (backs `get_market`).
- `GET https://clob.polymarket.com/prices-history?market=...` — historical prices (backs `get_price_history`).
Both APIs are public and require no authentication for these read paths.
## Notes on the Polymarket API
A few behaviors worth knowing if you're building on these endpoints
yourself, each reproduced directly against the live API:
- **`clobTokenIds` and `outcomes` are JSON-encoded strings, not arrays.**
A Gamma market object contains `"clobTokenIds": "[\"5144...\", \"6848...\"]"`,
a string that happens to look like a JSON array, not an actual array. Code
that calls `.map` or iterates on it directly will throw or silently do
nothing. It needs a `json.loads` first, with a fallback for malformed or
missing values.
- **`GET /markets?search=...` is a no-op.** It silently ignores the `search`
parameter and returns an unrelated page of markets. The only endpoint that
actually implements search is `GET /public-search?q=...`, the one behind
the website's search box.
- **The order book's price levels are not sorted the way you'd expect.**
`GET /book` returns both `bids` and `asks` in ascending price order. For
asks that puts the best price (lowest ask) first, which looks correct and
invites indexing `asks[0]`. For bids, ascending order puts the best price
(highest bid) *last*: `bids[0]` is the worst bid in the book, not the
touch. Both sides need an explicit sort before you can trust `[0]`.
- **`events_status=active` does not fully filter.** `/public-search` still
returns closed markets when asked for active ones, so the market's own
`closed` flag has to be re-checked client-side.
- **One-sided books are normal, not errors.** Illiquid, closed, or resolving
markets routinely return an empty `bids` or `asks` array with HTTP 200.
Best bid, best ask, mid, and spread all have to degrade to null rather
than raising, and mid/spread need *both* sides to mean anything.
- **`/prices-history` takes a token id in a parameter named `market`.**
Passing an actual condition_id there returns an empty history with no
error, which reads like a market with no trading rather than a bad call.
## Note on the MCP Python SDK version
The `mcp` dependency is pinned `>=1.9,<2` deliberately. SDK 2.0.0 removed the
`Server.list_tools` decorator, so any server written against the 1.x
low-level API dies at startup with
`AttributeError: 'Server' object has no attribute 'list_tools'`. If you are
debugging that error in another MCP server, an unbounded `mcp>=1.0.0` pin in
its dependencies is very likely the cause.
TDQS
Scored across 5 tools
Tools are mostly distinct: search_markets finds markets, get_market resolves a specific market, get_order_book provides full depth, get_quote provides a quick price snapshot, and get_price_history gives historical data. The only potential overlap is get_quote versus get_order_book, but the descriptions clearly differentiate a lightweight price check from full depth.
All tool names follow a consistent verb_noun pattern with lowercase and underscores: search_markets, get_market, get_order_book, get_quote, get_price_history. This is highly predictable and maintains a uniform style throughout.
Five tools is a well-scoped set for a market data server, covering search, resolution, current pricing (both depth and quick quote), and historical data. Each tool earns its place without unnecessary redundancy or bloat.
The server covers the core market data lifecycle: discovering markets, resolving identifiers, retrieving current order book depth, getting quick quotes, and accessing price history. Minor gaps like trade history or volume data exist, but the set is adequate for the stated purpose of a book-focused MCP.