#
tokens: 4864/50000 7/7 files
lines: off (toggle) GitHub
raw markdown copy
# 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
[![smithery badge](https://smithery.ai/badge/polymarket_mcp)](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>

[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/berlinbra-polymarket-mcp-badge.png)](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())
```