Skip to main content
Glama
solenyaresearch0000

MCP Server Kalshi

README.md
# MCP Server Kalshi

A Model Context Protocol (MCP) server for the Kalshi prediction market trading platform. This server provides automated access to market data, order management, and portfolio operations through Kalshi's REST API.

## Features

### Portfolio Management
- View account balance and portfolio value
- Track positions across all markets
- Monitor order history and fills
- Review settlement history

### Market Discovery
- Search and filter markets by various criteria
- Get real-time market prices and statistics
- View orderbook depth and bid/ask spreads
- Browse events and series

### Order Management
- Create buy/sell orders (market or limit)
- Cancel existing orders
- Amend orders atomically
- Submit batch orders (up to 20)

## Available MCP Tools

### Portfolio Tools

#### `get_balance`
Get the portfolio balance of the logged-in member in cents.

**Returns**: Available balance, portfolio value, total returns, etc.

**Example**:
```python
# No parameters required
balance = await get_balance()
print(f"Available balance: ${balance['balance'] / 100:.2f}")
```

#### `get_positions`
Get a list of all of your positions.

**Parameters**:
- `limit` (optional): Number of results per page (1-1000, default 100)
- `cursor` (optional): Pagination cursor for next page
- `status` (optional): Filter by position status
- `market_ticker` (optional): Filter by market ticker
- `event_ticker` (optional): Filter by event ticker

**Example**:
```python
positions = await get_positions({"limit": 50, "status": "open"})
```

#### `get_orders`
Get a list of all of your orders.

**Parameters**:
- `limit` (optional): Number of results per page (1-1000)
- `cursor` (optional): Pagination cursor
- `status` (optional): Filter by order status

**Example**:
```python
orders = await get_orders({"status": "open", "limit": 100})
```

#### `get_fills`
Get a list of all of your order fills.

**Parameters**:
- `limit` (optional): Number of results per page
- `cursor` (optional): Pagination cursor

#### `get_settlements`
Get a list of all of your settlements.

**Parameters**:
- `limit` (optional): Number of results per page
- `cursor` (optional): Pagination cursor

### Market Discovery Tools

#### `search_markets`
Search and filter Kalshi markets by various criteria (event, series, status, time ranges).

**Parameters**:
- `event_ticker` (optional): Filter by event ticker
- `series_ticker` (optional): Filter by series ticker
- `status` (optional): Market status filter ("open", "closed", "settled")
- `max_close_ts` (optional): Markets closing before this Unix timestamp (ms)
- `min_close_ts` (optional): Markets closing after this Unix timestamp (ms)
- `tickers` (optional): Comma-separated list of market tickers
- `limit` (optional): Results per page (1-1000, default 100)
- `cursor` (optional): Pagination cursor

**Example**:
```python
markets = await search_markets({
    "status": "open",
    "series_ticker": "PRES",
    "limit": 50
})
for market in markets['markets']:
    print(f"{market['ticker']}: ${market['yes_price']}¢")
```

#### `get_market`
Get detailed information about a specific market by ticker.

**Parameters**:
- `ticker` (required): Market ticker

**Returns**: Full market details including prices, volume, open interest, and status.

**Example**:
```python
market = await get_market({"ticker": "PRES-2025-WINNER-DEM"})
print(f"YES price: {market['yes_price'] / 100:.2%}")
print(f"Volume: {market['volume']}")
```

#### `get_orderbook`
Get current bid/ask spreads and orderbook depth for a market.

**Parameters**:
- `ticker` (required): Market ticker
- `depth` (optional): Number of price levels (1-100, default 5)

**Returns**: Orderbook with YES and NO side price levels.

**Example**:
```python
book = await get_orderbook({"ticker": "PRES-2025-WINNER-DEM", "depth": 10})
print(f"Best YES bid: {book['yes'][0]['price']}¢")
print(f"Best YES ask: {book['yes'][-1]['price']}¢")
```

### Event/Series Tools

#### `search_events`
Search for events (collections of related markets) by series or status.

**Parameters**:
- `series_ticker` (optional): Filter by series ticker
- `status` (optional): Event status filter
- `with_nested_markets` (optional): Include market data in response
- `limit` (optional): Results per page (1-1000, default 100)
- `cursor` (optional): Pagination cursor

**Example**:
```python
events = await search_events({"series_ticker": "PRES", "limit": 20})
for event in events['events']:
    print(f"{event['event_ticker']}: {event['title']}")
```

#### `get_event`
Get details about a specific event by its ticker.

**Parameters**:
- `event_ticker` (required): Event ticker

**Returns**: Event metadata including title, status, and category.

#### `get_series`
Get information about a market series (category/topic).

**Parameters**:
- `series_ticker` (required): Series ticker

**Returns**: Series metadata including title, category, frequency, and tags.

**Example**:
```python
series = await get_series({"series_ticker": "POLITICS"})
print(f"Series: {series['title']}")
print(f"Frequency: {series['frequency']}")
```

### Order Management Tools

#### `create_order`
Create an order to buy or sell contracts in a market.

**Parameters**:
- `action` (required): "buy" or "sell"
- `ticker` (required): Market ticker
- `side` (required): "yes" or "no"
- `count` (required): Number of contracts
- `type` (required): "market" or "limit"
- `yes_price` (optional): YES limit price in cents (1-99, required for limit orders)
- `no_price` (optional): NO limit price in cents (1-99, required for limit orders)
- `expiration_ts` (optional): Order expiration Unix timestamp (ms)
- `client_order_id` (optional): Client-specified order ID

**Example**:
```python
order = await create_order({
    "action": "buy",
    "ticker": "PRES-2025-WINNER-DEM",
    "side": "yes",
    "count": 10,
    "type": "limit",
    "yes_price": 52
})
```

#### `cancel_order`
Cancel an existing order by its order ID.

**Parameters**:
- `order_id` (required): Kalshi order UUID

**Returns**: Cancelled order confirmation.

**Example**:
```python
result = await cancel_order({"order_id": "abc-123-xyz"})
print(f"Cancelled order status: {result['order']['status']}")
```

#### `amend_order`
Modify an existing order's price or quantity (atomic cancel + create operation).

**Parameters**:
- `order_id` (required): Order UUID to amend
- `new_count` (optional): New contract count
- `new_yes_price` (optional): New YES price in cents (1-99)
- `new_no_price` (optional): New NO price in cents (1-99)

**Note**: At least one of `new_count`, `new_yes_price`, or `new_no_price` must be provided.

**Example**:
```python
new_order = await amend_order({
    "order_id": "abc-123-xyz",
    "new_yes_price": 55,
    "new_count": 20
})
print(f"Amended to new order: {new_order['order_id']}")
```

#### `batch_orders`
Submit multiple orders atomically (all succeed or all fail, max 20 orders).

**Parameters**:
- `orders` (required): List of order objects (max 20)

**Returns**: List of created order objects.

**Example**:
```python
result = await batch_orders({
    "orders": [
        {
            "action": "buy",
            "ticker": "MARKET-1",
            "side": "yes",
            "count": 10,
            "type": "limit",
            "yes_price": 50
        },
        {
            "action": "buy",
            "ticker": "MARKET-2",
            "side": "no",
            "count": 5,
            "type": "limit",
            "no_price": 45
        }
    ]
})
print(f"Created {len(result['orders'])} orders")
```

## Common Usage Scenarios

### Scenario 1: Browse Markets and Place a Trade

```python
# 1. Search for open markets in the PRES series
markets = await search_markets({
    "status": "open",
    "series_ticker": "PRES",
    "limit": 20
})

# 2. Get detailed information about a specific market
market = await get_market({"ticker": "PRES-2025-WINNER-DEM"})
print(f"Current YES price: {market['yes_price']}¢")
print(f"Volume: {market['volume']}")

# 3. Check the orderbook for liquidity
book = await get_orderbook({"ticker": "PRES-2025-WINNER-DEM", "depth": 5})
print(f"Best YES bid: {book['yes'][0]['price']}¢")

# 4. Place a limit order
order = await create_order({
    "action": "buy",
    "ticker": "PRES-2025-WINNER-DEM",
    "side": "yes",
    "count": 10,
    "type": "limit",
    "yes_price": 52
})
print(f"Order created: {order['order_id']}")
```

### Scenario 2: Monitor and Manage Active Positions

```python
# 1. Check account balance
balance = await get_balance()
print(f"Available: ${balance['balance'] / 100:.2f}")

# 2. View open positions
positions = await get_positions({"status": "open", "limit": 100})
for pos in positions['positions']:
    print(f"{pos['market_ticker']}: {pos['quantity']} contracts, PnL: ${pos['unrealized_pnl_cents'] / 100:.2f}")

# 3. View open orders
orders = await get_orders({"status": "open"})
for order in orders['orders']:
    print(f"{order['ticker']}: {order['count']} @ {order['yes_price']}¢")

# 4. Cancel or amend an order if needed
await cancel_order({"order_id": "abc-123-xyz"})
```

### Scenario 3: Explore Events and Series

```python
# 1. Browse available series
series = await get_series({"series_ticker": "POLITICS"})
print(f"Series: {series['title']}")

# 2. Find related events
events = await search_events({
    "series_ticker": "POLITICS",
    "status": "active",
    "limit": 50
})

# 3. Get markets for a specific event
event = await get_event({"event_ticker": "PRES-2025"})
markets = await search_markets({"event_ticker": "PRES-2025"})
```

### Scenario 4: Batch Trading

```python
# Submit multiple orders atomically (all or nothing)
result = await batch_orders({
    "orders": [
        {
            "action": "buy",
            "ticker": "MARKET-A",
            "side": "yes",
            "count": 10,
            "type": "limit",
            "yes_price": 50
        },
        {
            "action": "buy",
            "ticker": "MARKET-B",
            "side": "no",
            "count": 15,
            "type": "limit",
            "no_price": 45
        },
        {
            "action": "sell",
            "ticker": "MARKET-C",
            "side": "yes",
            "count": 5,
            "type": "limit",
            "yes_price": 60
        }
    ]
})
print(f"Successfully created {len(result['orders'])} orders")
```

## Configuration

### Claud Desktop
<details>
<summary>Setting up with UVX</summary>

```json
"mcpServers": {
  "kalshi": {
    "command": "uvx",
    "args": ["mcp-server-kalshi"],
    "env": {
        "KALSHI_PRIVATE_KEY_PATH": "PATH TO YOUR RSA KEY FILE",
        "KALSHI_API_KEY": "<YOUR KALSHI API KEY>",
        "BASE_URL": "https://api.elections.kalshi.com"
    }
  }
}
```
</details>

<details>
<summary>Setting up with Docker</summary>

1. Build the container from root directory `docker build -t mcp-server-kalshi .`

2. Configure client to run the container (ensure the bind command gives the container access to your rsa key files)
```json
"mcpServers": {
  "kalshi": {
    "command": "docker",
    "args": ["run", "--rm", "-i", "--mount", "type=bind,src=/Users/username,dst=/Users/username", "-e", "KALSHI_PRIVATE_KEY_PATH", "-e", "KALSHI_API_KEY","-e", "BASE_URL", "mcp-server-kalshi"],
    "env": {
        "KALSHI_PRIVATE_KEY_PATH": "PATH TO YOUR RSA KEY FILE",
        "KALSHI_API_KEY": "<YOUR KALSHI API KEY>",
        "BASE_URL": "https://api.elections.kalshi.com"
    }
  }
}
```
</details>


## Local Development
1. Create a `.env` file in the root directory with the following variables
   * `BASE_URL` The kalshi API URL
   * `KALSHI_API_KEY` The API key for the corresponding environment
   * `KALSHI_PRIVATE_KEY_PATH` A filepath to a file containing your Kalshi RSA private key

2. Install deps via `uv pip install -e .` Dev deps can be installed with `uv pip install -e .[dev]`
3. Run with `uv run start`

## Authentication Setup

### Getting Kalshi API Credentials

To use this MCP server, you'll need Kalshi API credentials:

1. **Production Environment** (real money):
   - Create a Kalshi account at [kalshi.com](https://kalshi.com)
   - Follow the [API Keys documentation](https://trading-api.readme.io/reference/api-keys) to generate:
     - An API Key (string)
     - An RSA Private Key (PEM file)
   - Set `BASE_URL=https://api.elections.kalshi.com`

2. **Demo Environment** (test account, recommended for development):
   - Create a demo account following [these instructions](https://trading-api.readme.io/reference/creating-a-demo-account)
   - Generate demo API credentials
   - Set `BASE_URL=https://demo-api.kalshi.co`

### Environment Variables

The server requires three environment variables:

| Variable | Description | Example |
|----------|-------------|---------|
| `KALSHI_API_KEY` | Your Kalshi API key string | `a1b2c3d4-e5f6-7890-abcd-ef1234567890` |
| `KALSHI_PRIVATE_KEY_PATH` | Absolute path to your RSA private key PEM file | `/Users/username/.kalshi/private_key.pem` |
| `BASE_URL` | Kalshi API base URL | `https://api.elections.kalshi.com` (prod) or `https://demo-api.kalshi.co` (demo) |

**Security Note**: Keep your private key secure and never commit it to version control. The server uses RSA-PSS with SHA-256 for authentication.


### UVX
To run in MCP inspector
```bash
npx @modelcontextprotocol/inspector uv --directory /path/to/your/mcp-server-kalshi run start
```

To run in claud desktop, update your MCP config to:
```json
{
    "mcpServers": {
        "kalshi": {
            "command": "uv",
            "args": [ 
            "--directory",
            "/<path to repo root directory>",
            "run",
            "start"
            ],
            "env": {
                "KALSHI_PRIVATE_KEY_PATH": "PATH TO YOUR RSA KEY FILE",
                "KALSHI_API_KEY": "<YOUR KALSHI API KEY>",
                "BASE_URL": "https://api.elections.kalshi.com"
            }
        }
    }
}
```