#
tokens: 6517/50000 7/7 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── pyproject.toml
├── README.md
├── smithery.yaml
└── src
    └── polymarket_mcp
        ├── __init__.py
        └── server.py
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python
 2 | __pycache__/
 3 | *.py[cod]
 4 | *$py.class
 5 | *.so
 6 | .Python
 7 | build/
 8 | develop-eggs/
 9 | dist/
10 | downloads/
11 | eggs/
12 | .eggs/
13 | lib/
14 | lib64/
15 | parts/
16 | sdist/
17 | var/
18 | wheels/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 | 
23 | # Virtual Environment
24 | venv/
25 | ENV/
26 | env/
27 | .env
28 | 
29 | # IDE
30 | .idea/
31 | .vscode/
32 | *.swp
33 | *.swo
34 | 
35 | # Other
36 | .DS_Store
37 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | 
  2 | # PolyMarket MCP Server
  3 | [![smithery badge](https://smithery.ai/badge/polymarket_mcp)](https://smithery.ai/server/polymarket_mcp)
  4 | 
  5 | A Model Context Protocol (MCP) server that provides access to prediction market data through the PolyMarket API. This server implements a standardized interface for retrieving market information, prices, and historical data from prediction markets.
  6 | 
  7 | <a href="https://glama.ai/mcp/servers/c255m147fd">
  8 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/c255m147fd/badge" alt="PolyMarket Server MCP server" />
  9 | </a>
 10 | 
 11 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/berlinbra-polymarket-mcp-badge.png)](https://mseep.ai/app/berlinbra-polymarket-mcp)
 12 | 
 13 | ## Features
 14 | 
 15 | - Real-time prediction market data with current prices and probabilities
 16 | - Detailed market information including categories, resolution dates, and descriptions
 17 | - Historical price and volume data with customizable timeframes (1d, 7d, 30d, all)
 18 | - Built-in error handling and rate limit management
 19 | - Clean data formatting for easy consumption
 20 | 
 21 | ## Installation
 22 | 
 23 | #### Installing via Smithery
 24 | 
 25 | To install PolyMarket Predictions for Claude Desktop automatically via [Smithery](https://smithery.ai/server/polymarket_mcp):
 26 | 
 27 | ```bash
 28 | npx -y @smithery/cli install polymarket_mcp --client claude
 29 | ```
 30 | 
 31 | #### Claude Desktop
 32 | - On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
 33 | - On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
 34 | 
 35 | <summary>Development/Unpublished Servers Configuration</summary>
 36 | 
 37 | ```json
 38 |     "mcpServers": {
 39 |         "polymarket-mcp": {
 40 |             "command": "uv",
 41 |             "args": [
 42 |             "--directory",
 43 |             "/Users/{INSERT_USER}/YOUR/PATH/TO/polymarket-mcp",
 44 |             "run",
 45 |             "polymarket-mcp" //or src/polymarket_mcp/server.py
 46 |             ],
 47 |             "env": {
 48 |                 "KEY": "<insert poly market api key>",
 49 |                 "FUNDER": "<insert polymarket wallet address>"
 50 |             }
 51 |         }
 52 |     }
 53 | ```
 54 | 
 55 | ### Running Locally
 56 | 1. Clone the repository and install dependencies:
 57 | 
 58 | #### Install Libraries
 59 | ```
 60 | uv pip install -e .
 61 | ```
 62 | 
 63 | ### Running 
 64 | After connecting Claude client with the MCP tool via json file and installing the packages, Claude should see the server's mcp tools:
 65 | 
 66 | You can run the sever yourself via:
 67 | In polymarket-mcp repo: 
 68 | ```
 69 | uv run src/polymarket_mcp/server.py
 70 | ```
 71 | 
 72 | *if you want to run the server inspector along with the server: 
 73 | ```
 74 | npx @modelcontextprotocol/inspector uv --directory C:\\Users\\{INSERT_USER}\\YOUR\\PATH\\TO\\polymarket-mcp run src/polymarket_mcp/server.py
 75 | ```
 76 | 
 77 | 2. Create a `.env` file with your PolyMarket API key:
 78 | ```
 79 | Key=your_api_key_here
 80 | Funder=poly market wallet address
 81 | ```
 82 | 
 83 | After connecting Claude client with the MCP tool via json file, run the server:
 84 | In alpha-vantage-mcp repo: `uv run src/polymarket_mcp/server.py`
 85 | 
 86 | 
 87 | ## Available Tools
 88 | 
 89 | The server implements four tools:
 90 | - `get-market-info`: Get detailed information about a specific prediction market
 91 | - `list-markets`: List available prediction markets with filtering options
 92 | - `get-market-prices`: Get current prices and trading information
 93 | - `get-market-history`: Get historical price and volume data
 94 | 
 95 | ### get-market-info
 96 | 
 97 | **Input Schema:**
 98 | ```json
 99 | {
100 |     "market_id": {
101 |         "type": "string",
102 |         "description": "Market ID or slug"
103 |     }
104 | }
105 | ```
106 | 
107 | **Example Response:**
108 | ```
109 | Title: Example Market
110 | Category: Politics
111 | Status: Open
112 | Resolution Date: 2024-12-31
113 | Volume: $1,234,567.89
114 | Liquidity: $98,765.43
115 | Description: This is an example prediction market...
116 | ---
117 | ```
118 | 
119 | ### list-markets
120 | 
121 | **Input Schema:**
122 | ```json
123 | {
124 |     "status": {
125 |         "type": "string",
126 |         "description": "Filter by market status",
127 |         "enum": ["open", "closed", "resolved"]
128 |     },
129 |     "limit": {
130 |         "type": "integer",
131 |         "description": "Number of markets to return",
132 |         "default": 10,
133 |         "minimum": 1,
134 |         "maximum": 100
135 |     },
136 |     "offset": {
137 |         "type": "integer",
138 |         "description": "Number of markets to skip (for pagination)",
139 |         "default": 0,
140 |         "minimum": 0
141 |     }
142 | }
143 | ```
144 | 
145 | **Example Response:**
146 | ```
147 | Available Markets:
148 | 
149 | ID: market-123
150 | Title: US Presidential Election 2024
151 | Status: Open
152 | Volume: $1,234,567.89
153 | ---
154 | 
155 | ID: market-124
156 | Title: Oscar Best Picture 2024
157 | Status: Open
158 | Volume: $234,567.89
159 | ---
160 | ```
161 | 
162 | ### get-market-prices
163 | 
164 | **Input Schema:**
165 | ```json
166 | {
167 |     "market_id": {
168 |         "type": "string",
169 |         "description": "Market ID or slug"
170 |     }
171 | }
172 | ```
173 | 
174 | **Example Response:**
175 | ```
176 | Current Market Prices for US Presidential Election 2024
177 | 
178 | Outcome: Democratic
179 | Price: $0.6500
180 | Probability: 65.0%
181 | ---
182 | 
183 | Outcome: Republican
184 | Price: $0.3500
185 | Probability: 35.0%
186 | ---
187 | ```
188 | 
189 | ### get-market-history
190 | 
191 | **Input Schema:**
192 | ```json
193 | {
194 |     "market_id": {
195 |         "type": "string",
196 |         "description": "Market ID or slug"
197 |     },
198 |     "timeframe": {
199 |         "type": "string",
200 |         "description": "Time period for historical data",
201 |         "enum": ["1d", "7d", "30d", "all"],
202 |         "default": "7d"
203 |     }
204 | }
205 | ```
206 | 
207 | **Example Response:**
208 | ```
209 | Historical Data for US Presidential Election 2024
210 | Time Period: 7d
211 | 
212 | Time: 2024-01-20T12:00:00Z
213 | Price: $0.6500
214 | Volume: $123,456.78
215 | ---
216 | 
217 | Time: 2024-01-19T12:00:00Z
218 | Price: $0.6300
219 | Volume: $98,765.43
220 | ---
221 | ```
222 | 
223 | ## Error Handling
224 | 
225 | The server includes comprehensive error handling for various scenarios:
226 | 
227 | - Rate limiting (429 errors)
228 | - Invalid API keys (403 errors)
229 | - Invalid market IDs (404 errors)
230 | - Network connectivity issues
231 | - API timeout conditions (30-second timeout)
232 | - Malformed responses
233 | 
234 | Error messages are returned in a clear, human-readable format.
235 | 
236 | ## Prerequisites
237 | 
238 | - Python 3.9 or higher
239 | - httpx>=0.24.0
240 | - mcp-core
241 | - python-dotenv>=1.0.0
242 | 
243 | ## Contributing
244 | 
245 | Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
```

--------------------------------------------------------------------------------
/src/polymarket_mcp/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | PolyMarket MCP Server
3 | An MCP server implementation for interacting with the PolyMarket API.
4 | """
5 | 
6 | __version__ = "0.1.0"
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [build-system]
 2 | requires = ["hatchling"]
 3 | build-backend = "hatchling.build"
 4 | 
 5 | [project]
 6 | name = "polymarket_mcp"
 7 | version = "0.1.0"
 8 | description = "MCP Server for PolyMarket API"
 9 | requires-python = ">=3.10"
10 | dependencies = [
11 |     "mcp>=0.1.0",
12 |     "httpx>=0.24.0",
13 |     "python-dotenv>=1.0.0",
14 |     "py-clob-client"
15 | ]
16 | 
17 | [project.scripts]
18 | polymarket-mcp = "polymarket_mcp:main"
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - polymarketApiKey
10 |     properties:
11 |       polymarketApiKey:
12 |         type: string
13 |         description: The API key for accessing the PolyMarket API.
14 |   commandFunction:
15 |     # A function that produces the CLI command to start the MCP on stdio.
16 |     |-
17 |     (config) => ({ command: 'python', args: ['src/polymarket_mcp/server.py'], env: { POLYMARKET_API_KEY: config.polymarketApiKey } })
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | # Use the official Python image from the Docker Hub
 3 | FROM python:3.10-slim
 4 | 
 5 | # Set the working directory
 6 | WORKDIR /app
 7 | 
 8 | # Copy the requirements and source code into the container
 9 | COPY pyproject.toml /app/
10 | COPY src /app/src
11 | 
12 | # Install dependencies
13 | RUN pip install --no-cache-dir --upgrade pip \
14 |     && pip install --no-cache-dir hatchling
15 | 
16 | # Install the project in the container
17 | RUN pip install --no-cache-dir /app
18 | 
19 | # Define environment variables
20 | ENV POLYMARKET_API_KEY="<insert api key>"
21 | 
22 | # Run the server
23 | CMD ["python", "src/polymarket_mcp/server.py"]
```

--------------------------------------------------------------------------------
/src/polymarket_mcp/server.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import Any
  2 | import asyncio
  3 | import httpx
  4 | import json
  5 | from mcp.server.models import InitializationOptions
  6 | import mcp.types as types
  7 | from mcp.server import NotificationOptions, Server
  8 | import mcp.server.stdio
  9 | import os
 10 | from dotenv import load_dotenv
 11 | from py_clob_client.client import ClobClient
 12 | from py_clob_client.clob_types import OrderArgs
 13 | from py_clob_client.constants import POLYGON
 14 | 
 15 | # Load environment variables
 16 | load_dotenv()
 17 | 
 18 | server = Server("polymarket_predictions")
 19 | 
 20 | # Initialize CLOB client
 21 | def get_clob_client() -> ClobClient:
 22 |     host = os.getenv("CLOB_HOST", "https://clob.polymarket.com")
 23 |     key = os.getenv("KEY")  # Private key exported from polymarket UI
 24 |     funder = os.getenv("FUNDER")  # Funder address from polymarket UI
 25 |     chain_id = POLYGON
 26 |     
 27 |     client = ClobClient(
 28 |         host,
 29 |         key=key,
 30 |         chain_id=POLYGON,
 31 |         funder=funder,
 32 |         signature_type=1,
 33 |     )
 34 |     client.set_api_creds(client.create_or_derive_api_creds())
 35 |     return client
 36 | 
 37 | @server.list_tools()
 38 | async def handle_list_tools() -> list[types.Tool]:
 39 |     """
 40 |     List available tools for interacting with the PolyMarket API.
 41 |     Each tool specifies its arguments using JSON Schema validation.
 42 |     """
 43 |     return [
 44 |         types.Tool(
 45 |             name="get-market-info",
 46 |             description="Get detailed information about a specific prediction market",
 47 |             inputSchema={
 48 |                 "type": "object",
 49 |                 "properties": {
 50 |                     "market_id": {
 51 |                         "type": "string",
 52 |                         "description": "Market ID or slug",
 53 |                     },
 54 |                 },
 55 |                 "required": ["market_id"],
 56 |             },
 57 |         ),
 58 |         types.Tool(
 59 |             name="list-markets",
 60 |             description="Get a list of prediction markets with optional filters",
 61 |             inputSchema={
 62 |                 "type": "object",
 63 |                 "properties": {
 64 |                     "status": {
 65 |                         "type": "string",
 66 |                         "description": "Filter by market status (e.g., open, closed, resolved)",
 67 |                         "enum": ["active", "resolved"],
 68 |                     },
 69 |                     "limit": {
 70 |                         "type": "integer",
 71 |                         "description": "Number of markets to return (default: 10)",
 72 |                         "default": 10,
 73 |                         "minimum": 1,
 74 |                         "maximum": 100
 75 |                     },
 76 |                     "offset": {
 77 |                         "type": "integer",
 78 |                         "description": "Number of markets to skip (for pagination)",
 79 |                         "default": 0,
 80 |                         "minimum": 0
 81 |                     }
 82 |                 },
 83 |             },
 84 |         ),
 85 |         types.Tool(
 86 |             name="get-market-prices",
 87 |             description="Get current prices and trading information for a market",
 88 |             inputSchema={
 89 |                 "type": "object",
 90 |                 "properties": {
 91 |                     "market_id": {
 92 |                         "type": "string",
 93 |                         "description": "Market ID or slug",
 94 |                     },
 95 |                 },
 96 |                 "required": ["market_id"],
 97 |             },
 98 |         ),
 99 |         types.Tool(
100 |             name="get-market-history",
101 |             description="Get historical price and volume data for a market",
102 |             inputSchema={
103 |                 "type": "object",
104 |                 "properties": {
105 |                     "market_id": {
106 |                         "type": "string",
107 |                         "description": "Market ID or slug",
108 |                     },
109 |                     "timeframe": {
110 |                         "type": "string",
111 |                         "description": "Time period for historical data",
112 |                         "enum": ["1d", "7d", "30d", "all"],
113 |                         "default": "7d"
114 |                     }
115 |                 },
116 |                 "required": ["market_id"],
117 |             },
118 |         )
119 |     ]
120 | 
121 | def format_market_info(market_data: dict) -> str:
122 |     """Format market information into a concise string."""
123 |     try:
124 |         if not market_data or not isinstance(market_data, dict):
125 |             return "No market information available"
126 |             
127 |         condition_id = market_data.get('condition_id', 'N/A')
128 |         title = market_data.get('title', 'N/A')
129 |         status = market_data.get('status', 'N/A')
130 |         resolution_date = market_data.get('resolution_date', 'N/A')
131 |             
132 |         return (
133 |             f"Condition ID: {condition_id}\n"
134 |             f"Title: {title}\n"
135 |             f"Status: {status}\n"
136 |             f"Resolution Date: {resolution_date}\n"
137 |             "---"
138 |         )
139 |     except Exception as e:
140 |         return f"Error formatting market data: {str(e)}"
141 | 
142 | def format_market_list(markets_data: list) -> str:
143 |     """Format list of markets into a concise string."""
144 |     try:
145 |         if not markets_data:
146 |             return "No markets available"
147 |             
148 |         formatted_markets = ["Available Markets:\n"]
149 |         
150 |         for market in markets_data:
151 |             try:
152 |                 volume = float(market.get('volume', 0))
153 |                 volume_str = f"${volume:,.2f}"
154 |             except (ValueError, TypeError):
155 |                 volume_str = f"${market.get('volume', 0)}"
156 |                 
157 |             formatted_markets.append(
158 |                 f"Condition ID: {market.get('condition_id', 'N/A')}\n"
159 |                 f"Description: {market.get('description', 'N/A')}\n"
160 |                 f"Category: {market.get('category', 'N/A')}\n"
161 |                 f"Tokens: {market.get('question', 'N/A')}\n"
162 |                 f"Question: {market.get('active', 'N/A')}\n"
163 |                 f"Rewards: {market.get('rewards', 'N/A')}\n"
164 |                 f"Active: {market.get('active', 'N/A')}\n"
165 |                 f"Closed: {market.get('closed', 'N/A')}\n"
166 |                 f"Slug: {market.get('market_slug', 'N/A')}\n"
167 |                 f"Min Incentive size: {market.get('min_incentive_size', 'N/A')}\n"
168 |                 f"Max Incentive size: {market.get('max_incentive_spread', 'N/A')}\n"
169 |                 f"End date: {market.get('end_date_iso', 'N/A')}\n"
170 |                 f"Start time: {market.get('game_start_time', 'N/A')}\n"
171 |                 f"Min order size: {market.get('minimum_order_size', 'N/A')}\n"
172 |                 f"Max tick size: {market.get('minimum_tick_size', 'N/A')}\n"
173 |                 f"Volume: {volume_str}\n"
174 |                 "---\n"
175 |             )
176 |         
177 |         return "\n".join(formatted_markets)
178 |     except Exception as e:
179 |         return f"Error formatting markets list: {str(e)}"
180 | 
181 | def format_market_prices(market_data: dict) -> str:
182 |     """Format market prices into a concise string."""
183 |     try:
184 |         if not market_data or not isinstance(market_data, dict):
185 |             return market_data
186 |             
187 |         formatted_prices = [
188 |             f"Current Market Prices for {market_data.get('title', 'Unknown Market')}\n"
189 |         ]
190 |         
191 |         # Extract price information from market data
192 |         # Note: Adjust this based on actual CLOB client response structure
193 |         current_price = market_data.get('current_price', 'N/A')
194 |         formatted_prices.append(
195 |             f"Current Price: {current_price}\n"
196 |             "---\n"
197 |         )
198 |         
199 |         return "\n".join(formatted_prices)
200 |     except Exception as e:
201 |         return f"Error formatting price data: {str(e)}"
202 | 
203 | def format_market_history(history_data: dict) -> str:
204 |     """Format market history data into a concise string."""
205 |     try:
206 |         if not history_data or not isinstance(history_data, dict):
207 |             return "No historical data available"
208 |             
209 |         formatted_history = [
210 |             f"Historical Data for {history_data.get('title', 'Unknown Market')}\n"
211 |         ]
212 |         
213 |         # Format historical data points
214 |         # Note: Adjust this based on actual CLOB client response structure
215 |         for point in history_data.get('history', [])[-5:]:
216 |             formatted_history.append(
217 |                 f"Time: {point.get('timestamp', 'N/A')}\n"
218 |                 f"Price: {point.get('price', 'N/A')}\n"
219 |                 "---\n"
220 |             )
221 |         
222 |         return "\n".join(formatted_history)
223 |     except Exception as e:
224 |         return f"Error formatting historical data: {str(e)}"
225 | 
226 | @server.call_tool()
227 | async def handle_call_tool(
228 |     name: str, arguments: dict | None
229 | ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
230 |     """
231 |     Handle tool execution requests.
232 |     Tools can fetch prediction market data and notify clients of changes.
233 |     """
234 |     if not arguments:
235 |         return [types.TextContent(type="text", text="Missing arguments for the request")]
236 |     
237 |     client = get_clob_client()
238 |     
239 |     try:
240 |         if name == "get-market-info":
241 |             market_id = arguments.get("market_id")
242 |             if not market_id:
243 |                 return [types.TextContent(type="text", text="Missing market_id parameter")]
244 |             
245 |             market_data = client.get_market(market_id)
246 |             formatted_info = format_market_info(market_data)
247 |             return [types.TextContent(type="text", text=formatted_info)]
248 | 
249 |         elif name == "list-markets":
250 |             status = arguments.get("status")
251 |             
252 |             # Get markets using CLOB client
253 |             markets_data = client.get_markets()
254 | 
255 |             # Handle string response (if the response is a JSON string)
256 |             if isinstance(markets_data, str):
257 |                 try:
258 |                     markets_data = json.loads(markets_data)
259 |                 except json.JSONDecodeError:
260 |                     return [types.TextContent(type="text", text="Error: Invalid response format from API")]
261 |             
262 |             # Ensure we have a list of markets
263 |             if not isinstance(markets_data, list):
264 |                 if isinstance(markets_data, dict) and 'data' in markets_data:
265 |                     markets_data = markets_data['data']
266 |                 else:
267 |                     return [types.TextContent(type="text", text="Error: Unexpected response format from API")]
268 |             
269 |             # Filter by status if specified
270 |             if status:
271 |                 markets_data = [
272 |                     market for market in markets_data 
273 |                     if isinstance(market, dict) and market.get('status', '').lower() == status.lower()
274 |                 ]
275 |             
276 |             # Apply pagination
277 |             offset = arguments.get("offset", 0)
278 |             limit = arguments.get("limit", 10)
279 |             markets_data = markets_data[offset:offset + limit]
280 |             
281 |             formatted_list = format_market_list(markets_data)
282 |             return [types.TextContent(type="text", text=formatted_list)]
283 | 
284 |         elif name == "get-market-prices":
285 |             market_id = arguments.get("market_id")
286 |             if not market_id:
287 |                 return [types.TextContent(type="text", text="Missing market_id parameter")]
288 |             
289 |             market_data = client.get_market(market_id)
290 |             formatted_prices = format_market_prices(market_data)
291 |             return [types.TextContent(type="text", text=formatted_prices)]
292 | 
293 |         elif name == "get-market-history":
294 |             market_id = arguments.get("market_id")
295 |             timeframe = arguments.get("timeframe", "7d")
296 |             
297 |             if not market_id:
298 |                 return [types.TextContent(type="text", text="Missing market_id parameter")]
299 |             
300 |             # Note: Adjust this based on actual CLOB client capabilities
301 |             market_data = client.get_market(market_id)
302 |             formatted_history = format_market_history(market_data)
303 |             return [types.TextContent(type="text", text=formatted_history)]
304 |             
305 |         else:
306 |             return [types.TextContent(type="text", text=f"Unknown tool: {name}")]
307 |             
308 |     except Exception as e:
309 |         return [types.TextContent(type="text", text=f"Error executing tool: {str(e)}")]
310 | 
311 | async def main():
312 |     """Main entry point for the MCP server."""
313 |     async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
314 |         await server.run(
315 |             read_stream,
316 |             write_stream,
317 |             InitializationOptions(
318 |                 server_name="polymarket_predictions",
319 |                 server_version="0.1.0",
320 |                 capabilities=server.get_capabilities(
321 |                     notification_options=NotificationOptions(),
322 |                     experimental_capabilities={},
323 |                 ),
324 |             ),
325 |         )
326 | 
327 | if __name__ == "__main__":
328 |     asyncio.run(main())
```