# Directory Structure ``` ├── .gitignore ├── Dockerfile ├── LICENSE ├── pyproject.toml ├── README.md ├── smithery.yaml └── src └── polymarket_mcp ├── __init__.py └── server.py ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Python __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Virtual Environment venv/ ENV/ env/ .env # IDE .idea/ .vscode/ *.swp *.swo # Other .DS_Store ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # PolyMarket MCP Server [](https://smithery.ai/server/polymarket_mcp) 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. <a href="https://glama.ai/mcp/servers/c255m147fd"> <img width="380" height="200" src="https://glama.ai/mcp/servers/c255m147fd/badge" alt="PolyMarket Server MCP server" /> </a> [](https://mseep.ai/app/berlinbra-polymarket-mcp) ## Features - Real-time prediction market data with current prices and probabilities - Detailed market information including categories, resolution dates, and descriptions - Historical price and volume data with customizable timeframes (1d, 7d, 30d, all) - Built-in error handling and rate limit management - Clean data formatting for easy consumption ## Installation #### Installing via Smithery To install PolyMarket Predictions for Claude Desktop automatically via [Smithery](https://smithery.ai/server/polymarket_mcp): ```bash npx -y @smithery/cli install polymarket_mcp --client claude ``` #### Claude Desktop - On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json` - On Windows: `%APPDATA%/Claude/claude_desktop_config.json` <summary>Development/Unpublished Servers Configuration</summary> ```json "mcpServers": { "polymarket-mcp": { "command": "uv", "args": [ "--directory", "/Users/{INSERT_USER}/YOUR/PATH/TO/polymarket-mcp", "run", "polymarket-mcp" //or src/polymarket_mcp/server.py ], "env": { "KEY": "<insert poly market api key>", "FUNDER": "<insert polymarket wallet address>" } } } ``` ### Running Locally 1. Clone the repository and install dependencies: #### Install Libraries ``` uv pip install -e . ``` ### Running After connecting Claude client with the MCP tool via json file and installing the packages, Claude should see the server's mcp tools: You can run the sever yourself via: In polymarket-mcp repo: ``` uv run src/polymarket_mcp/server.py ``` *if you want to run the server inspector along with the server: ``` npx @modelcontextprotocol/inspector uv --directory C:\\Users\\{INSERT_USER}\\YOUR\\PATH\\TO\\polymarket-mcp run src/polymarket_mcp/server.py ``` 2. Create a `.env` file with your PolyMarket API key: ``` Key=your_api_key_here Funder=poly market wallet address ``` After connecting Claude client with the MCP tool via json file, run the server: In alpha-vantage-mcp repo: `uv run src/polymarket_mcp/server.py` ## Available Tools The server implements four tools: - `get-market-info`: Get detailed information about a specific prediction market - `list-markets`: List available prediction markets with filtering options - `get-market-prices`: Get current prices and trading information - `get-market-history`: Get historical price and volume data ### get-market-info **Input Schema:** ```json { "market_id": { "type": "string", "description": "Market ID or slug" } } ``` **Example Response:** ``` Title: Example Market Category: Politics Status: Open Resolution Date: 2024-12-31 Volume: $1,234,567.89 Liquidity: $98,765.43 Description: This is an example prediction market... --- ``` ### list-markets **Input Schema:** ```json { "status": { "type": "string", "description": "Filter by market status", "enum": ["open", "closed", "resolved"] }, "limit": { "type": "integer", "description": "Number of markets to return", "default": 10, "minimum": 1, "maximum": 100 }, "offset": { "type": "integer", "description": "Number of markets to skip (for pagination)", "default": 0, "minimum": 0 } } ``` **Example Response:** ``` Available Markets: ID: market-123 Title: US Presidential Election 2024 Status: Open Volume: $1,234,567.89 --- ID: market-124 Title: Oscar Best Picture 2024 Status: Open Volume: $234,567.89 --- ``` ### get-market-prices **Input Schema:** ```json { "market_id": { "type": "string", "description": "Market ID or slug" } } ``` **Example Response:** ``` Current Market Prices for US Presidential Election 2024 Outcome: Democratic Price: $0.6500 Probability: 65.0% --- Outcome: Republican Price: $0.3500 Probability: 35.0% --- ``` ### get-market-history **Input Schema:** ```json { "market_id": { "type": "string", "description": "Market ID or slug" }, "timeframe": { "type": "string", "description": "Time period for historical data", "enum": ["1d", "7d", "30d", "all"], "default": "7d" } } ``` **Example Response:** ``` Historical Data for US Presidential Election 2024 Time Period: 7d Time: 2024-01-20T12:00:00Z Price: $0.6500 Volume: $123,456.78 --- Time: 2024-01-19T12:00:00Z Price: $0.6300 Volume: $98,765.43 --- ``` ## Error Handling The server includes comprehensive error handling for various scenarios: - Rate limiting (429 errors) - Invalid API keys (403 errors) - Invalid market IDs (404 errors) - Network connectivity issues - API timeout conditions (30-second timeout) - Malformed responses Error messages are returned in a clear, human-readable format. ## Prerequisites - Python 3.9 or higher - httpx>=0.24.0 - mcp-core - python-dotenv>=1.0.0 ## Contributing 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 """ PolyMarket MCP Server An MCP server implementation for interacting with the PolyMarket API. """ __version__ = "0.1.0" ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "polymarket_mcp" version = "0.1.0" description = "MCP Server for PolyMarket API" requires-python = ">=3.10" dependencies = [ "mcp>=0.1.0", "httpx>=0.24.0", "python-dotenv>=1.0.0", "py-clob-client" ] [project.scripts] polymarket-mcp = "polymarket_mcp:main" ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml startCommand: type: stdio configSchema: # JSON Schema defining the configuration options for the MCP. type: object required: - polymarketApiKey properties: polymarketApiKey: type: string description: The API key for accessing the PolyMarket API. commandFunction: # A function that produces the CLI command to start the MCP on stdio. |- (config) => ({ command: 'python', args: ['src/polymarket_mcp/server.py'], env: { POLYMARKET_API_KEY: config.polymarketApiKey } }) ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile # Use the official Python image from the Docker Hub FROM python:3.10-slim # Set the working directory WORKDIR /app # Copy the requirements and source code into the container COPY pyproject.toml /app/ COPY src /app/src # Install dependencies RUN pip install --no-cache-dir --upgrade pip \ && pip install --no-cache-dir hatchling # Install the project in the container RUN pip install --no-cache-dir /app # Define environment variables ENV POLYMARKET_API_KEY="<insert api key>" # Run the server CMD ["python", "src/polymarket_mcp/server.py"] ``` -------------------------------------------------------------------------------- /src/polymarket_mcp/server.py: -------------------------------------------------------------------------------- ```python from typing import Any import asyncio import httpx import json from mcp.server.models import InitializationOptions import mcp.types as types from mcp.server import NotificationOptions, Server import mcp.server.stdio import os from dotenv import load_dotenv from py_clob_client.client import ClobClient from py_clob_client.clob_types import OrderArgs from py_clob_client.constants import POLYGON # Load environment variables load_dotenv() server = Server("polymarket_predictions") # Initialize CLOB client def get_clob_client() -> ClobClient: host = os.getenv("CLOB_HOST", "https://clob.polymarket.com") key = os.getenv("KEY") # Private key exported from polymarket UI funder = os.getenv("FUNDER") # Funder address from polymarket UI chain_id = POLYGON client = ClobClient( host, key=key, chain_id=POLYGON, funder=funder, signature_type=1, ) client.set_api_creds(client.create_or_derive_api_creds()) return client @server.list_tools() async def handle_list_tools() -> list[types.Tool]: """ List available tools for interacting with the PolyMarket API. Each tool specifies its arguments using JSON Schema validation. """ return [ types.Tool( name="get-market-info", description="Get detailed information about a specific prediction market", inputSchema={ "type": "object", "properties": { "market_id": { "type": "string", "description": "Market ID or slug", }, }, "required": ["market_id"], }, ), types.Tool( name="list-markets", description="Get a list of prediction markets with optional filters", inputSchema={ "type": "object", "properties": { "status": { "type": "string", "description": "Filter by market status (e.g., open, closed, resolved)", "enum": ["active", "resolved"], }, "limit": { "type": "integer", "description": "Number of markets to return (default: 10)", "default": 10, "minimum": 1, "maximum": 100 }, "offset": { "type": "integer", "description": "Number of markets to skip (for pagination)", "default": 0, "minimum": 0 } }, }, ), types.Tool( name="get-market-prices", description="Get current prices and trading information for a market", inputSchema={ "type": "object", "properties": { "market_id": { "type": "string", "description": "Market ID or slug", }, }, "required": ["market_id"], }, ), types.Tool( name="get-market-history", description="Get historical price and volume data for a market", inputSchema={ "type": "object", "properties": { "market_id": { "type": "string", "description": "Market ID or slug", }, "timeframe": { "type": "string", "description": "Time period for historical data", "enum": ["1d", "7d", "30d", "all"], "default": "7d" } }, "required": ["market_id"], }, ) ] def format_market_info(market_data: dict) -> str: """Format market information into a concise string.""" try: if not market_data or not isinstance(market_data, dict): return "No market information available" condition_id = market_data.get('condition_id', 'N/A') title = market_data.get('title', 'N/A') status = market_data.get('status', 'N/A') resolution_date = market_data.get('resolution_date', 'N/A') return ( f"Condition ID: {condition_id}\n" f"Title: {title}\n" f"Status: {status}\n" f"Resolution Date: {resolution_date}\n" "---" ) except Exception as e: return f"Error formatting market data: {str(e)}" def format_market_list(markets_data: list) -> str: """Format list of markets into a concise string.""" try: if not markets_data: return "No markets available" formatted_markets = ["Available Markets:\n"] for market in markets_data: try: volume = float(market.get('volume', 0)) volume_str = f"${volume:,.2f}" except (ValueError, TypeError): volume_str = f"${market.get('volume', 0)}" formatted_markets.append( f"Condition ID: {market.get('condition_id', 'N/A')}\n" f"Description: {market.get('description', 'N/A')}\n" f"Category: {market.get('category', 'N/A')}\n" f"Tokens: {market.get('question', 'N/A')}\n" f"Question: {market.get('active', 'N/A')}\n" f"Rewards: {market.get('rewards', 'N/A')}\n" f"Active: {market.get('active', 'N/A')}\n" f"Closed: {market.get('closed', 'N/A')}\n" f"Slug: {market.get('market_slug', 'N/A')}\n" f"Min Incentive size: {market.get('min_incentive_size', 'N/A')}\n" f"Max Incentive size: {market.get('max_incentive_spread', 'N/A')}\n" f"End date: {market.get('end_date_iso', 'N/A')}\n" f"Start time: {market.get('game_start_time', 'N/A')}\n" f"Min order size: {market.get('minimum_order_size', 'N/A')}\n" f"Max tick size: {market.get('minimum_tick_size', 'N/A')}\n" f"Volume: {volume_str}\n" "---\n" ) return "\n".join(formatted_markets) except Exception as e: return f"Error formatting markets list: {str(e)}" def format_market_prices(market_data: dict) -> str: """Format market prices into a concise string.""" try: if not market_data or not isinstance(market_data, dict): return market_data formatted_prices = [ f"Current Market Prices for {market_data.get('title', 'Unknown Market')}\n" ] # Extract price information from market data # Note: Adjust this based on actual CLOB client response structure current_price = market_data.get('current_price', 'N/A') formatted_prices.append( f"Current Price: {current_price}\n" "---\n" ) return "\n".join(formatted_prices) except Exception as e: return f"Error formatting price data: {str(e)}" def format_market_history(history_data: dict) -> str: """Format market history data into a concise string.""" try: if not history_data or not isinstance(history_data, dict): return "No historical data available" formatted_history = [ f"Historical Data for {history_data.get('title', 'Unknown Market')}\n" ] # Format historical data points # Note: Adjust this based on actual CLOB client response structure for point in history_data.get('history', [])[-5:]: formatted_history.append( f"Time: {point.get('timestamp', 'N/A')}\n" f"Price: {point.get('price', 'N/A')}\n" "---\n" ) return "\n".join(formatted_history) except Exception as e: return f"Error formatting historical data: {str(e)}" @server.call_tool() async def handle_call_tool( name: str, arguments: dict | None ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: """ Handle tool execution requests. Tools can fetch prediction market data and notify clients of changes. """ if not arguments: return [types.TextContent(type="text", text="Missing arguments for the request")] client = get_clob_client() try: if name == "get-market-info": market_id = arguments.get("market_id") if not market_id: return [types.TextContent(type="text", text="Missing market_id parameter")] market_data = client.get_market(market_id) formatted_info = format_market_info(market_data) return [types.TextContent(type="text", text=formatted_info)] elif name == "list-markets": status = arguments.get("status") # Get markets using CLOB client markets_data = client.get_markets() # Handle string response (if the response is a JSON string) if isinstance(markets_data, str): try: markets_data = json.loads(markets_data) except json.JSONDecodeError: return [types.TextContent(type="text", text="Error: Invalid response format from API")] # Ensure we have a list of markets if not isinstance(markets_data, list): if isinstance(markets_data, dict) and 'data' in markets_data: markets_data = markets_data['data'] else: return [types.TextContent(type="text", text="Error: Unexpected response format from API")] # Filter by status if specified if status: markets_data = [ market for market in markets_data if isinstance(market, dict) and market.get('status', '').lower() == status.lower() ] # Apply pagination offset = arguments.get("offset", 0) limit = arguments.get("limit", 10) markets_data = markets_data[offset:offset + limit] formatted_list = format_market_list(markets_data) return [types.TextContent(type="text", text=formatted_list)] elif name == "get-market-prices": market_id = arguments.get("market_id") if not market_id: return [types.TextContent(type="text", text="Missing market_id parameter")] market_data = client.get_market(market_id) formatted_prices = format_market_prices(market_data) return [types.TextContent(type="text", text=formatted_prices)] elif name == "get-market-history": market_id = arguments.get("market_id") timeframe = arguments.get("timeframe", "7d") if not market_id: return [types.TextContent(type="text", text="Missing market_id parameter")] # Note: Adjust this based on actual CLOB client capabilities market_data = client.get_market(market_id) formatted_history = format_market_history(market_data) return [types.TextContent(type="text", text=formatted_history)] else: return [types.TextContent(type="text", text=f"Unknown tool: {name}")] except Exception as e: return [types.TextContent(type="text", text=f"Error executing tool: {str(e)}")] async def main(): """Main entry point for the MCP server.""" async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, InitializationOptions( server_name="polymarket_predictions", server_version="0.1.0", capabilities=server.get_capabilities( notification_options=NotificationOptions(), experimental_capabilities={}, ), ), ) if __name__ == "__main__": asyncio.run(main()) ```