Polymarket Agent MCP
by polytutor-ai
README.md
# Polymarket Agent MCP
MCP server that lets a local AI client read Polymarket markets and, when you turn live mode on, place orders with your wallet. Version 1.7.0. Node.js 20 or newer. MIT license.
The process starts in **preview**. Preview writes simulated trades to a local SQLite file and does not sign or send orders. **Live** mode signs CLOB orders with `POLY_PRIVATE_KEY` and posts them to `https://clob.polymarket.com`. One process, one database, one wallet. It is a single-operator tool.
Stdio is the transport for Claude Code, Cursor, and other local MCP clients. HTTP is optional and should stay on a private network.
Capability details: [PERMISSIONS.md](PERMISSIONS.md) and [SECURITY.md](SECURITY.md).
<p align="center">
<img src="./assets/agent-mcp-flow.png" width="100%" alt="Flow of the Polymarket Agent MCP: local client, 48 tools, market data, safety limits, preview mode, live CLOB orders, the copy loop, and private-deployment limits" />
</p>
## What a call does
1. The client invokes a tool. Inputs are checked with Zod.
2. Read tools call the public Polymarket APIs, or ESPN when the tool is `wta.discover`.
3. Trade tools run the safety checks, then the daily budget check.
4. In preview, the trade row is stored as `simulated`.
5. In live, `TradeExecutor` builds a CLOB client, derives API credentials from the signing key, and calls `createAndPostOrder`. The position row is stored as `executed` only after the CLOB accepts the order.
`config.go_live` flips the running process to live after `confirm: true`, and only if the live credentials are set. A restart returns to `COPY_MODE` from the environment.
## Install
```bash
npm install -g polymarket-agent-mcp
```
From this repository:
```bash
git clone https://github.com/polytutor-ai/polymarket-mcp-agent.git
cd polymarket-agent-mcp
npm install
npm run build
npm test
```
Claude Code, in `~/.claude/settings.json`:
```json
{
"mcpServers": {
"polymarket": {
"command": "npx",
"args": ["polymarket-agent-mcp"]
}
}
}
```
Copy `.env.example` to `.env` beside the package when you want a budget other than the defaults, or when you want live trading. The loader reads `.env` from the package root, not from the process working directory.
## Modes
| Mode | How it starts | Orders |
|------|----------------|--------|
| Preview | Default. `COPY_MODE=preview` | Simulated rows in SQLite |
| Live | `COPY_MODE=live`, or `config.go_live` with `confirm: true` | Signed CLOB orders |
Live startup requires all four of these to be non-empty:
```bash
POLY_PRIVATE_KEY=
POLY_API_KEY=
POLY_API_SECRET=
POLY_API_PASSPHRASE=
POLY_FUNDER_ADDRESS=
COPY_MODE=live
```
The executor signs with `POLY_PRIVATE_KEY` and then calls `createOrDeriveApiKey()`. The CLOB client is constructed with signature type `1` and `POLY_FUNDER_ADDRESS` as the funder, which matches a Polymarket proxy wallet. `CHAIN_ID` defaults to `137` (Polygon).
Keys stay in the process config. They are not written to SQLite. Error text that looks like a long hex key is redacted before it is logged. The designated order host is `clob.polymarket.com`.
## Copy monitor
`monitor.start` polls watched wallets on an interval (default 30 seconds, allowed range 10–300). Each tick reads recent BUY activity from `data-api.polymarket.com`. A trade is eligible when it is newer than the monitor’s max age and its USDC size is at least `MIN_CONVICTION` (default $3). The copy size is then limited by the remaining daily budget (`DAILY_BUDGET`, default $20).
`monitor.stop` clears that loop. The watchlist survives in SQLite.
`watchlist.rebalance` drops watched addresses that fail the performance rules in that tool. `traders.backtest` replays a wallet’s past trades locally and does not send orders.
## Safety limits
`config.safety_limits` stores limits in the `config` table. Defaults until you change them:
| Limit | Default |
|-------|---------|
| `max_order_size` | $50 |
| `max_exposure` | $200 |
| `max_per_market` | $100 |
| `min_liquidity` | $50 |
| `max_spread` | 0.10 |
`orders.buy` rejects a ticket that would exceed max order size, total recorded exposure, or the per-market total. Market-quality checks used by buy and the monitor look at spread, depth, and price range separately. Stop-loss and take-profit levels are stored with `positions.set_exit_rules` and evaluated when something calls `positions.check_exits`. They are not a background loop of their own.
## Network
| Host | When |
|------|------|
| `gamma-api.polymarket.com` | Market search, metadata, resolution |
| `data-api.polymarket.com` | Leaderboard, positions, wallet activity |
| `clob.polymarket.com` | Books, prices, and live order writes |
| `wss://ws-subscriptions-clob.polymarket.com/ws/market` | Public prices after `markets.watch_price` |
| `site.api.espn.com` | Today’s WTA scoreboard inside `wta.discover` |
HTTP reads use a 10 second timeout and up to two retries. The websocket sends a public market subscription. It does not send the wallet key. There is no analytics or update-check client in this package.
## HTTP transport
HTTP starts when the process is given `--http` or when `PORT` is set. The Docker image does both: `CMD` is `node dist/index.js --http` and `PORT=3000`.
```bash
node dist/index.js --http
PORT=8080 node dist/index.js
```
| Path | Role |
|------|------|
| `POST /mcp` | Streamable HTTP MCP |
| `GET /health` | `{ status, version, db }` |
| `GET /.well-known/mcp/server-card.json` | Discovery card |
| `GET /` | Name, version, and the paths above |
`MCP_API_KEY`, when set, requires `Authorization: Bearer <token>` on `/mcp`. When it is unset, `/mcp` accepts any caller who can open the port. The listen socket is all interfaces. Keep HTTP on localhost, or set `MCP_API_KEY` and do not publish the port.
```bash
docker build -t polymarket-mcp .
docker run -p 127.0.0.1:3000:3000 \
-v mcp-data:/app/data \
-e COPY_MODE=preview \
-e DAILY_BUDGET=20 \
-e MCP_API_KEY=replace-me \
polymarket-mcp
```
A release image is published as `ghcr.io/demwick/polymarket-agent-mcp:1.7.0`.
`docker compose up` builds the local Dockerfile, mounts `./data` at `/app/data`, and sets preview mode. The Compose file does not publish port 3000. The container still runs the HTTP command from the Dockerfile. Add a `ports` mapping yourself if you want to reach it from the host.
SQLite path: `DB_PATH`, default `./copytrader.db`, or `/app/data/copytrader.db` in the image. The file holds the watchlist, trades, daily budget, safety config, exit rules, and agent-cycle notes. It does not hold the signing key.
## Configuration
| Variable | Default | Role |
|----------|---------|------|
| `COPY_MODE` | `preview` | `preview` or `live` |
| `DAILY_BUDGET` | `20` | USDC ceiling for the UTC day |
| `MIN_CONVICTION` | `3` | Smallest source trade, in USDC, that the monitor will copy |
| `POLY_PRIVATE_KEY` | empty | Signer. Required for live |
| `POLY_API_KEY` | empty | Required to pass the live credential check |
| `POLY_API_SECRET` | empty | Required to pass the live credential check |
| `POLY_API_PASSPHRASE` | empty | Required to pass the live credential check |
| `POLY_FUNDER_ADDRESS` | empty | Proxy funder on the CLOB client |
| `CHAIN_ID` | `137` | Chain id passed to the CLOB client |
| `MCP_API_KEY` | empty | Bearer token for `/mcp` |
| `DB_PATH` | `./copytrader.db` | SQLite file |
| `PORT` | unset | Setting it enables HTTP on that port |
`config.set` can change `daily_budget` and `min_conviction` in the database while the process is running. It cannot change the signing key or `COPY_MODE`.
## Tools
Forty-eight tools are registered in `src/index.ts`. Names below are the MCP tool ids.
### Markets
| Tool | Behavior |
|------|----------|
| `markets.search` | Keyword search |
| `markets.discover` | Markets by end date and category |
| `markets.trending` | Ranked by 24h, 7d, or 30d volume |
| `markets.featured` | Liquid markets in a category such as politics, sports, or crypto |
| `markets.related` | Markets related to a topic or condition |
| `markets.events` | Markets that belong to one event |
| `markets.price` | Bid, ask, mid, spread |
| `markets.price_history` | History and a text sparkline, from 1 hour to 1 month |
| `markets.check` | Spread, depth, and price-range quality |
| `markets.holders` | Largest holders |
| `markets.compare` | Two to five markets side by side |
| `markets.analyze` | Heuristic BUY, SELL, or HOLD from price, quality, and recent momentum. No external LLM |
| `markets.arbitrage` | YES plus NO prices that fail to sum near 1 |
| `markets.watch` | Saved markets and price-alert levels |
| `markets.watch_price` | Live CLOB websocket subscription |
| `wta.discover` | Today’s WTA matches from ESPN, matched to Polymarket slugs, with a suggested discount bid |
| `wta.bid` | Limit bid at that discount. Preview unless the process is live |
| `flow.discover` | Markets where several leaderboard wallets are buying |
### Traders and copy
| Tool | Behavior |
|------|----------|
| `traders.discover` | Leaderboard by PnL, volume, and ROI. Optional `auto_watch` |
| `traders.analyze` | Profile, win rate, and PnL for one address |
| `traders.score` | 0–100 score across the dimensions implemented in `score-trader` |
| `traders.positions` | Open positions for one address |
| `traders.backtest` | Local replay of copying that address |
| `watchlist.add` | Add or remove an address |
| `watchlist.list` | Current watchlist |
| `watchlist.rebalance` | Remove addresses that fail the rebalance rules |
| `monitor.start` | Start the copy loop |
| `monitor.stop` | Stop the copy loop |
### Orders and positions
| Tool | Behavior |
|------|----------|
| `orders.buy` | Buy outcome shares for a condition id. Minimum amount $0.50 |
| `orders.sell` | Sell an open position |
| `orders.batch` | Up to 10 orders in one call |
| `orders.cancel` | Cancel all open CLOB orders. Requires a live client |
| `orders.list` | Open orders |
| `orders.status` | One order by id |
| `positions.list` | Open and closed rows |
| `positions.close` | Close one local position |
| `positions.set_exit_rules` | Store stop-loss and take-profit prices |
| `positions.check_exits` | Evaluate those rules and market resolution |
| `config.go_live` | Switch this process to live. `confirm` must be true |
### Portfolio and config
| Tool | Behavior |
|------|----------|
| `portfolio.balance` | Collateral view, budget, and PnL |
| `portfolio.get` | Open positions grouped by watched wallet |
| `portfolio.risk` | Concentration and limit warnings from recorded trades |
| `portfolio.optimize` | Suggestions for `conservative`, `balanced`, or `aggressive` thresholds. Does not send orders |
| `dashboard.get` | Text dashboard |
| `trades.history` | Filtered trade log |
| `config.set` | `daily_budget` or `min_conviction` |
| `config.safety_limits` | Read or update the limits above |
| `agent.log_cycle` | Append a note to `agent_cycles` for the dashboard |
Two prompts are registered for clients that support them: `daily-trading-cycle` and `evaluate-trader`. They return a checklist of tool calls. They do not place orders by themselves.
## Layout
```
src/
index.ts # tool registration, stdio, HTTP
constants.ts # Polymarket base URLs
tools/ # one module per tool
services/ # executor, monitor, books, backtest, WTA
db/ # SQLite schema and queries
utils/ # config, safety, fetch, logging
```
Logs go to stderr so stdio MCP can keep stdout for JSON-RPC. The last 500 lines are also kept in memory and can be forwarded to the MCP client as logging messages.
## Scripts
```bash
npm run build # tsc
npm start # node dist/index.js
npm run start:http # node dist/index.js --http
npm test # vitest
npx tsc --noEmit # typecheck; CI runs this before publish
```
`package.json` engines require Node.js `>=20`. `prepublishOnly` runs the TypeScript build. The only install scripts in the lockfile are `better-sqlite3` (native module) and optional macOS `fsevents`.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues