Kalshi Prediction Markets

Official registry Security

MCP server for Kalshi prediction markets: native RSA-PSS auth, rate limiting, demo/prod safety.

What can you do with Kalshi Prediction Markets?

  • kalshi_get_orderbooks(tickers=[…], depth=5) fetches up to 25 books in
  • kalshi_get_series_summary() rolls the whole listing up to one row per
  • kalshi_get_batch_candlesticks(market_tickers=[…]) fetches OHLC bars for
  • kalshi_get_combo_collections / kalshi_get_combo_collection — the parlay
  • kalshi_get_combo_events — combos already listed against a collection, i.e.
  • kalshi_get_combo_legs(ticker) resolves a KXMVE… combo into its
  • kalshi_create_combo_market — write. Materializes a combo market

Install

Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).

PYPI package kalshi-mcp-server
Claude Code
claude mcp add kalshi-prediction-markets -e 'KALSHI_API_KEY_ID=<KALSHI_API_KEY_ID>' -e 'KALSHI_PRIVATE_KEY_PATH=<KALSHI_PRIVATE_KEY_PATH>' -e 'KALSHI_PRIVATE_KEY_PEM=<your kalshi_private_key_pem>' -e 'KALSHI_ENV=<KALSHI_ENV>' -e 'KALSHI_ALLOW_PROD=<KALSHI_ALLOW_PROD>' -e 'KALSHI_TRADING_ENABLED=<KALSHI_TRADING_ENABLED>' -- uvx kalshi-mcp-server==0.1.8
JSON configuration (Claude Desktop, Cursor, VS Code)
{
    "mcpServers": {
        "kalshi-prediction-markets": {
            "command": "uvx",
            "args": [
                "kalshi-mcp-server==0.1.8"
            ],
            "env": {
                "KALSHI_API_KEY_ID": "<KALSHI_API_KEY_ID>",
                "KALSHI_PRIVATE_KEY_PATH": "<KALSHI_PRIVATE_KEY_PATH>",
                "KALSHI_PRIVATE_KEY_PEM": "<your kalshi_private_key_pem>",
                "KALSHI_ENV": "<KALSHI_ENV>",
                "KALSHI_ALLOW_PROD": "<KALSHI_ALLOW_PROD>",
                "KALSHI_TRADING_ENABLED": "<KALSHI_TRADING_ENABLED>"
            }
        }
    }
}

Environment variables

  • KALSHI_API_KEY_ID required Kalshi API key ID from https://kalshi.com/account/profile
  • KALSHI_PRIVATE_KEY_PATH Filesystem path to the RSA private key PEM. Set this or KALSHI_PRIVATE_KEY_PEM.
  • KALSHI_PRIVATE_KEY_PEM secret Inline PEM contents, for hosted deploys without filesystem access.
  • KALSHI_ENV demo or prod. Defaults to demo for safety.
  • KALSHI_ALLOW_PROD Must be 1 for the server to start with KALSHI_ENV=prod. Defaults to 0.
  • KALSHI_TRADING_ENABLED Must be 1 for order-placement tools to operate. Read-only by default (0).
OCI package ghcr.io/cejor6/kalshi-mcp-server:0.1.8
Claude Code
claude mcp add kalshi-prediction-markets -e 'KALSHI_API_KEY_ID=<KALSHI_API_KEY_ID>' -e 'KALSHI_PRIVATE_KEY_PEM=<your kalshi_private_key_pem>' -e 'KALSHI_ENV=<KALSHI_ENV>' -e 'KALSHI_ALLOW_PROD=<KALSHI_ALLOW_PROD>' -e 'KALSHI_TRADING_ENABLED=<KALSHI_TRADING_ENABLED>' -- docker run -i --rm ghcr.io/cejor6/kalshi-mcp-server:0.1.8
JSON configuration (Claude Desktop, Cursor, VS Code)
{
    "mcpServers": {
        "kalshi-prediction-markets": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "ghcr.io/cejor6/kalshi-mcp-server:0.1.8"
            ],
            "env": {
                "KALSHI_API_KEY_ID": "<KALSHI_API_KEY_ID>",
                "KALSHI_PRIVATE_KEY_PEM": "<your kalshi_private_key_pem>",
                "KALSHI_ENV": "<KALSHI_ENV>",
                "KALSHI_ALLOW_PROD": "<KALSHI_ALLOW_PROD>",
                "KALSHI_TRADING_ENABLED": "<KALSHI_TRADING_ENABLED>"
            }
        }
    }
}

Environment variables

  • KALSHI_API_KEY_ID required Kalshi API key ID from https://kalshi.com/account/profile
  • KALSHI_PRIVATE_KEY_PEM secret Inline PEM contents, for hosted deploys without filesystem access.
  • KALSHI_ENV demo or prod. Defaults to demo for safety.
  • KALSHI_ALLOW_PROD Must be 1 for the server to start with KALSHI_ENV=prod. Defaults to 0.
  • KALSHI_TRADING_ENABLED Must be 1 for order-placement tools to operate. Read-only by default (0).

Documentation

From the project's README on GitHub, refreshed weekly. Links open on GitHub.

Contents
  1. kalshi-mcp-server
  2. Quickstart (30 seconds)
  3. Why this server
  4. Install
  5. Configure
  6. Use with an MCP client (stdio)
  7. Use as a remote MCP service
  8. Tools
  9. Resources
  10. Safety model
  11. Deployment
  12. Contributing
  13. License
  14. Acknowledgments

kalshi-mcp-server

📦 PyPI · 🗂️ MCP Registry · 🐳 Container image · 🚀 Deploy guide

A Model Context Protocol server for Kalshi prediction markets. Native RSA-PSS auth, async token-bucket rate limiting, two-step prepare/confirm order flow with safety caps, optional bundled OAuth proxy for remote-MCP deployments, 26 tools + 4 resources across REST and WebSocket. MIT, designed to be forked.

Works with any MCP client — locally via stdio (Claude Desktop, Claude Code, Cursor, Zed, Continue, Cline, Goose, etc.) or remotely as a self-hosted HTTP server (claude.ai custom connectors today, any OAuth-capable MCP client in the future).

⚠️ This software lets an LLM place trades. Read DISCLAIMER.md before deploying. Trading prediction markets involves substantial risk of loss. AI agents make mistakes — sometimes confidently. The authors are not liable for any losses. Test in demo (KALSHI_ENV=demo, KALSHI_TRADING_ENABLED=0) until you understand the failure modes.

Status — alpha. Auth (REST + WS), rate limiting, safety controls, 26 tools across REST + live channels, and 4 resources are in place. A long-lived multiplexed WebSocket session and kalshi://markets/{ticker}/orderbook live resource are planned for v0.2.


Quickstart (30 seconds)

Read-only against Kalshi's demo environment — no real money, no trading flag. This is the safe way to try it.

pipx install kalshi-mcp-server        # or: pip install kalshi-mcp-server

Point any MCP client at it (this is the Claude Desktop / Claude Code shape — see the full client matrix for others):

{
  "mcpServers": {
    "kalshi": {
      "command": "kalshi-mcp",
      "args": ["--env-file", "/Users/you/.kalshi/.env"]
    }
  }
}

Minimal ~/.kalshi/.env (get a demo key at demo.kalshi.co — it's shown once):

KALSHI_API_KEY_ID=<your-demo-key-id>
KALSHI_PRIVATE_KEY_PATH=/absolute/path/to/demo_private_key.pem
KALSHI_ENV=demo

Restart the client and ask it to run a Kalshi tool. Enabling prod and trading is a deliberate opt-in — a few more flags (KALSHI_ALLOW_PROD=1, KALSHI_TRADING_ENABLED=1) — see Configure and the safety model.

What a call looks like

Ask the agent for tradeable markets and kalshi_find_liquid_markets returns a volume-ranked, combo-excluded shortlist (trimmed, illustrative):

{
  "scanned": 300,
  "markets": [
    {
      "ticker": "KXNBAGAME-25JUL12BOSLAL-BOS",
      "title": "Will the Celtics beat the Lakers?",
      "yes_bid_dollars": 0.58, "yes_ask_dollars": 0.60,
      "volume_24h_fp": 41230, "open_interest_fp": 88400,
      "status": "active", "close_time": "2026-07-12T23:30:00Z"
    },
    {
      "ticker": "KXHIGHNY-26JUL12-B90.5",
      "title": "Will NYC's high temp exceed 90.5°F today?",
      "yes_bid_dollars": 0.31, "yes_ask_dollars": 0.34,
      "volume_24h_fp": 12760, "open_interest_fp": 23110,
      "status": "active", "close_time": "2026-07-13T04:00:00Z"
    }
  ]
}

Placing a trade is a deliberate two step — kalshi_prepare_order runs the local safety checks and hands back a confirmation_id; nothing reaches Kalshi until you call kalshi_confirm_order with that token. An LLM can't place an order in a single call.


Why this server

Most existing Kalshi MCPs are thin wrappers around a handful of REST endpoints. This one aims to be:

  • Native Kalshi. Real RSA-PSS signer that handles the gotchas (path-without-query-string, ms timestamps, separate demo/prod keys).
  • Rate-limit aware. Client-side token bucket mirrors Kalshi's 2026 read/write budget model, so the server can't spam the API into a 429.
  • Safe by default. Refuses to start against prod without an explicit opt-in flag. Refuses to write without a separate trading-enabled flag. Order-time controls (size cap, daily cap, cash reserve) are all operator-configurable.
  • Hosted-deploy friendly. Accepts the private key as either a file path OR an env var with inline PEM, so it works on platforms without filesystem mounts.
  • Fork-able. MIT, no personal data, CI/CD set up so PR contributions flow through main without ever triggering a production deploy — only tagged releases (v*) do. Your fork's deployment stays decoupled from this repo's, and your fork's contributors can't affect what you run.

Install

Published as kalshi-mcp-server. pipx installs the kalshi-mcp entrypoint into its own isolated environment:

pipx install kalshi-mcp-server        # or: pip install kalshi-mcp-server
From source
git clone https://github.com/cejor6/kalshi-mcp-server.git
cd kalshi-mcp-server
uv sync
Docker

Multi-arch (amd64 + arm64) images are published to GHCR on every tagged release, tagged :latest and :vX.Y.Z:

docker pull ghcr.io/cejor6/kalshi-mcp-server:latest

See DEPLOY.md for hosted deployment.

Configure

  1. Generate a Kalshi API key at https://kalshi.com/account/profile (or the demo equivalent at https://demo.kalshi.co/account/profile). Save the private key — it is shown ONCE.
  2. Put your secrets in one .env file. A good location for the MCP-client use case is ~/.kalshi/.env (outside any repo). For local dev, the repo's own .env (gitignored) works too.
cp .env.example ~/.kalshi/.env
# edit ~/.kalshi/.env
  1. At minimum, set:
KALSHI_API_KEY_ID=<your-key-id>
KALSHI_PRIVATE_KEY_PATH=/absolute/path/to/your_kalshi_private_key.pem
KALSHI_ENV=demo

For prod, also set:

KALSHI_ENV=prod
KALSHI_ALLOW_PROD=1
KALSHI_TRADING_ENABLED=1   # only if you want writes
How env vars are resolved

On startup, the server resolves config in this order (highest wins):

  1. Values already in the process environment — set in the MCP client config's env: block, or exported in your shell.
  2. .env file — loaded from --env-file PATH if you pass that flag, otherwise from ./.env in the current working directory if it exists. Variables already in the environment from step 1 are not overridden.

So you can put secrets either inline in the MCP config (env:) or in a file the config points at (--env-file). You don't need to do both.

Use with an MCP client (stdio)

Every MCP stdio client uses the same shape: a command to launch the server, optional args, optional env. The differences are just the file/UI where you put the config.

Three install patterns work — pick whichever fits your environment.

Installs kalshi-mcp to a globally-available, isolated environment. pipx is the modern Python tool for this:

pipx install kalshi-mcp-server

MCP client config then collapses to:

{
  "mcpServers": {
    "kalshi": {
      "command": "kalshi-mcp",
      "args": ["--env-file", "/Users/you/.kalshi/.env"]
    }
  }
}

Update with pipx upgrade kalshi-mcp-server when you want the latest.

Pattern B — uv run against a local clone

Best if you've cloned the repo and have uv installed. Point the MCP client at uv with --directory:

{
  "mcpServers": {
    "kalshi": {
      "command": "uv",
      "args": [
        "run",
        "--directory", "/absolute/path/to/kalshi-mcp-server",
        "kalshi-mcp",
        "--env-file", "/Users/you/.kalshi/.env"
      ]
    }
  }
}

uv run activates the project's venv automatically. Update with git pull + restart the MCP client. Useful for development / hacking on the server itself.

Pattern C — Docker against the public image

Best for users without Python installed, or who prefer container isolation:

{
  "mcpServers": {
    "kalshi": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "-v", "/Users/you/.kalshi/demo.pem:/secrets/demo.pem:ro",
        "-e", "KALSHI_API_KEY_ID=<your-key-id>",
        "-e", "KALSHI_PRIVATE_KEY_PATH=/secrets/demo.pem",
        "-e", "KALSHI_ENV=demo",
        "ghcr.io/cejor6/kalshi-mcp-server:latest"
      ]
    }
  }
}

The -v mount bind-mounts your PEM file read-only into the container; KALSHI_PRIVATE_KEY_PATH points at that path. Secrets live in the JSON config — fine for a single-user machine.

Where to put this config:
ClientConfig location
Claude Desktopclaude_desktop_config.json (Settings → Developer)
Claude Codeproject .mcp.json or ~/.claude/mcp.json
CursorSettings → MCP → Add new MCP Server (UI fills the same JSON)
Zed~/.config/zed/settings.json under context_servers
Continue~/.continue/config.json under experimental.modelContextProtocolServers
ClineCline settings → MCP Servers → Edit JSON
Goose~/.config/goose/config.yaml under extensions

If you'd rather inline secrets in the MCP config (acceptable for local dev where the config file is on your own machine):

{
  "mcpServers": {
    "kalshi": {
      "command": "kalshi-mcp",
      "env": {
        "KALSHI_API_KEY_ID": "your-key-id",
        "KALSHI_PRIVATE_KEY_PATH": "/path/to/your/private_key.pem",
        "KALSHI_ENV": "demo"
      }
    }
  }
}

Why not just .env in the project dir? MCP clients spawn the server as a subprocess from their own working directory (typically your home dir on macOS/Linux, the client's install dir on Windows), so a .env sitting in this repo wouldn't get found. Hence --env-file to point at it explicitly. Running the server directly from the project dir (no client) still works without flags — the CLI auto-loads ./.env when launched there.

Use as a remote MCP service

For clients that don't speak local stdio — currently the main one being claude.ai's custom connector form, which only supports OAuth-protected HTTP — host the server somewhere reachable and point the client at it. The OAuth proxy is bundled with the server; you just need to configure it.

The proxy lets any GitHub account sign in, so HTTP transport also requires an identity allowlist — MCP_ALLOWED_GITHUB_IDS (numeric ids, preferred: they survive renames) and/or MCP_ALLOWED_GITHUB_LOGINS — enforced on every MCP request (tool calls, resource reads such as kalshi://balance, and listings). HTTP refuses to start on a partial OAuth config, and unauthenticated HTTP (MCP_ALLOW_INSECURE_HTTP=1) is confined to a loopback bind with DNS-rebinding protection unless you also set MCP_ALLOW_INSECURE_PUBLIC_BIND=1.

See DEPLOY.md for an end-to-end walkthrough using Render + GitHub OAuth + Upstash Redis. Other image-deploy hosts (Fly.io, Cloud Run, ECS, Railway) work the same way — Render is just the worked example.

Tools

GroupTools
Exchange / accountkalshi_get_exchange_status, kalshi_get_exchange_schedule, kalshi_get_api_limits, kalshi_get_environment, kalshi_set_safety_limits
Discoverykalshi_get_markets, kalshi_find_liquid_markets, kalshi_get_market, kalshi_get_event, kalshi_get_events, kalshi_get_series, kalshi_get_series_list, kalshi_get_series_summary, kalshi_get_milestones, kalshi_get_trades
Market datakalshi_get_orderbook, kalshi_get_orderbooks, kalshi_get_market_candlesticks, kalshi_get_event_candlesticks, kalshi_get_batch_candlesticks, kalshi_get_event_forecast_history, kalshi_get_market_trades
Combos / parlayskalshi_get_combo_collections, kalshi_get_combo_collection, kalshi_get_combo_events, kalshi_get_combo_legs, kalshi_create_combo_market (write — off by default, see below)
Portfoliokalshi_get_balance, kalshi_get_positions, kalshi_get_orders, kalshi_get_fills, kalshi_get_settlements
Orders (write)kalshi_prepare_order, kalshi_confirm_order, kalshi_cancel_order, kalshi_decrease_order, kalshi_get_order
Live (WebSocket)kalshi_get_live_orderbook, kalshi_sample_trades
External data (read-only)kalshi_fetch_external_data — host-allowlisted, GET-only, https-only fetch of public data feeds (Polymarket gamma/clob, NWS api.weather.gov, Open-Meteo incl. ensemble, Tennis Abstract, Deribit public). No credentials attached (trust_env=False), redirects not followed, body size- and wall-clock-capped (the cap applies to decoded bytes; Accept-Encoding: identity is sent) and returned wrapped in UNTRUSTED-EXTERNAL-DATA delimiters stamped with a per-call random nonce (<<<UNTRUSTED-EXTERNAL-DATA id=<nonce> …>>> … <<<END-UNTRUSTED-EXTERNAL-DATA id=<nonce>>>); the block ends only at the END marker whose id matches. Inside the body (matched after NFKC normalization with Unicode format characters removed), untrusted/external/data separated only by non-alphanumerics — any case — becomes [delimiter-removed], and any run of 3+ < or > becomes [angle-brackets-removed]. Cross-script homoglyphs are not covered; the nonce is the primary defence. Exists so clients whose own egress is restricted (e.g. claude.ai cloud routines) can reach the public feeds their read-only research needs; the allowlist is enforced at runtime, additions are a code change, and the boundary rationale lives in AGENTS.md.
Scoring (read-only, optional)kalshi_score_markets — ranks liquid markets by apparent edge using Jev (a fast typed-decision model), for a trading loop that scores many markets cheaply and escalates only the top few. Places no orders. Registered only when MCP_ALLOW_JEV_SCORING=1 (default off). Jev is optional: with no TYPESAFE_API_KEY, or on any Jev failure, markets fall back to a deterministic liquidity/spread heuristic (jev_scored=false). See "Optional Jev scoring" below.

Order placement requires KALSHI_TRADING_ENABLED=1. kalshi_prepare_order runs local safety checks and returns a confirmation_id; nothing is sent to Kalshi until you call kalshi_confirm_order with that token, which re-runs the checks against the limits in force at that moment. Cancel and decrease bypass the trading-enabled flag — they only reduce exposure.

Listing markets for an LLM: kalshi_get_markets / kalshi_get_market accept minimal=true to project each market down to a small whitelist of triage fields (ticker, prices, sizes, volume, status, close time). Prefer this over compact=true for scanning — compact is a blacklist and barely shrinks multivariate (KXMVE…) combo markets, whose bulk lives in custom_strike / mve_selected_legs / long sub-titles. Pass a custom fields="ticker,yes_bid_dollars,…" to override the default whitelist. View precedence is fields > minimal > compact > full. kalshi_get_event / kalshi_get_events accept the same minimal / fields for their nested markets (the event objects themselves only have the compact view). kalshi_get_event defaults to compact=true (it used to return the event and its nested markets in full): the verbose blacklist (rules text, previous prices, extra timestamps) is dropped, strikes and result are kept. Pass compact=false for the full objects, or minimal=true for the whitelist. A blank fields ("", " , ") is rejected by every tool before any request is made.

Don't gate on liquidity_dollars: Kalshi currently returns it as 0.0000 on every market, even deep books — measure liquidity from the orderbook (best bid/ask + resting size) plus volume_24h_fp / open_interest_fp. It is stripped from compact and minimal views.

Finding tradeable markets: the default open listing is dominated by multivariate (KXMVE…) combo markets with empty/one-sided books. Pass mve_filter="exclude" to kalshi_get_markets to drop them server-side, or use kalshi_find_liquid_markets — it excludes combos, ranks by 24h volume, and returns a short minimal-projection shortlist. (Kalshi has no server-side sort, so the helper's ranking is over a bounded scan window, reported as scanned in the result.) Pass scan_all=true to sweep the FULL open listing before ranking — that turns the shortlist into a genuine exchange-wide top-N rather than the top of an arbitrary slice. The sweep is bounded by internal request / wall-clock / market caps, and the result reports complete plus stopped_by so a partial scan is never mistaken for an exhaustive one.

Scanning wide without burning context: three tools exist for scan/anomaly workloads that would otherwise cost one call per market.

  • kalshi_get_orderbooks(tickers=[…], depth=5) fetches up to 25 books in one request, with per-ticker error isolation — a bad or event-level ticker becomes an error entry for that ticker instead of failing the batch. Kalshi's batch endpoint has no depth parameter, so depth is applied server-side by this MCP; it keeps the best levels (Kalshi returns levels ascending by price and both sides are bids).
  • kalshi_get_series_summary() rolls the whole listing up to one row per series (market count, event count, 24h volume, tightest spread, soonest close). Cheap in context, not free in reads — it's the daily "new supply census" that spots a new event class listing without paging thousands of markets. Note series_ticker is derived from the ticker prefix: Kalshi does not return it on market objects.
  • kalshi_get_batch_candlesticks(market_tickers=[…]) fetches OHLC bars for up to 100 markets in one request — a momentum lens over a whole shortlist. Its budget shape differs from the single-market tool: Kalshi returns at most 10,000 candles total across all tickers, so window cost multiplies by ticker count. Validated locally, with a message naming how many markets the requested window actually affords.

Combos / parlays. The multivariate surface lives in its own module:

  • kalshi_get_combo_collections / kalshi_get_combo_collection — the parlay families on offer and the rules each imposes (size_min / size_max legs, is_all_yes, the eligible-event universe).
  • kalshi_get_combo_events — combos already listed against a collection, i.e. live parlay supply. Defaults minimal=True for nested markets, because combo markets are the largest objects Kalshi returns.
  • kalshi_get_combo_legs(ticker) resolves a KXMVE… combo into its underlying legs (market ticker, side, title) from mve_selected_legs — strictly better than splitting the combo's title string on commas, which drops the leg tickers and mis-splits on titles that contain a comma. If Kalshi published no leg breakdown, it returns a structured resolvable: false rather than guessing. The legs it returns feed straight back into the create tool, so you can round-trip an existing combo and vary one leg. With include_collection=true the parent collection is attached with its eligible-event universe replaced by associated_event_count (pass include_collection_event_tickers=true for the full ~800-entry arrays — the old default).
  • kalshi_create_combo_market — write. Materializes a combo market ticker for a chosen leg set. It places no order and commits no money, so it has its own gate rather than riding on KALSHI_TRADING_ENABLED: set MCP_ALLOW_COMBO_CREATION=1 to register it at all (default off, and when off the tool isn't advertised to the model). Kalshi allows 5000 creations per week per account, so the server also enforces MCP_MAX_COMBO_CREATIONS_PER_DAY (default 100, in-process, resets at UTC midnight and on restart) to stop a retry loop burning the weekly quota. Before the POST it pre-flights your leg set against the collection's own size_min/size_max/is_all_yes rules, which Kalshi otherwise rejects with an opaque 400; that check fails open if the collection can't be read.

Two env vars come with the combo write surface — add them to your .env:

MCP_ALLOW_COMBO_CREATION=0        # 1 registers kalshi_create_combo_market
MCP_MAX_COMBO_CREATIONS_PER_DAY=100

Event ticker vs market ticker: a market ticker carries an outcome suffix (…PITHOU-HOU); an event ticker (…PITHOU) does not. Passing an event ticker to kalshi_get_market / kalshi_get_orderbook / kalshi_get_markets used to fail silently (404, or an empty book/list read as "no liquidity"). These tools now detect that case and raise an actionable hint naming the real market tickers instead.

Optional Jev scoring

kalshi_score_markets is an opt-in, read-only ranking aid for a trading loop that wants to score many markets cheaply and escalate only the top few to an expensive reasoning model. It reuses the liquid-market scan, builds a compact plaintext situation per market (title, quoted YES/NO prices, spread, volume, time-to-close, resolution-rule snippet), and asks Jev — TypeSafe's fast typed-decision model — a batched question set per market (an edge score plus a side choice) in one call. Results come back ranked by confidence * edge. It places no orders and commits no money.

Two gates, both fail safe:

  • Registration. Off by default. Set MCP_ALLOW_JEV_SCORING=1 to register the tool at all (same pattern as MCP_ALLOW_COMBO_CREATION — when off, the tool isn't advertised to the model). A default clone stays a pure Kalshi surface.
  • Jev is optional — never a dependency. Even when registered, if TYPESAFE_API_KEY is unset the tool ranks by a deterministic liquidity/spread heuristic with jev_scored=false. Every Jev call is wrapped in a timeout budget and falls back to that heuristic on any failure — 402 (out of credits), 429 (rate-limited), other non-2xx, network error, timeout, malformed/missing answer, or confidence below the threshold. The whole fan-out is also bounded by an aggregate wall-clock budget, so a slow host degrades to the heuristic rather than blocking, and a 429 trips a short global back-off so a rate-limited upstream isn't hammered. The tool never crashes; genuine failures are logged once per scan and returned as fallback_reasons.

No new hard dependency: the Jev call is plain REST over the httpx this server already ships — there is no typesafe-sdk requirement. Only public Kalshi market data is sent to Jev (no account data, balances, or secrets), and the API key is sent only to the configured Jev host over https. Like kalshi_fetch_external_data, this is a deliberate, documented exception to the "Kalshi surface only" boundary (see AGENTS.md) — it exists because the egress-restricted clients that consume this server can't reach api.typesafe.ai directly.

Env vars (all optional; add to your .env only if you enable scoring):

MCP_ALLOW_JEV_SCORING=0            # 1 registers kalshi_score_markets
TYPESAFE_API_KEY=                  # Jev key; unset => heuristic-only fallback
MCP_JEV_CONFIDENCE_THRESHOLD=0.6   # below this, a market falls back
MCP_JEV_MODEL=jev-latest
MCP_JEV_TIMEOUT_SECONDS=8          # per-request budget
MCP_JEV_RATE_COOLDOWN_SECONDS=60   # global back-off after a 429 (capped at 1h)
# MCP_JEV_BASE_URL=https://api.typesafe.ai/v1/systemone  # override for self-host/tests

Resources

URIDescription
kalshi://environmentCurrent env, safety limits in force + their env ceilings, rate-limit headroom (no API call)
kalshi://balanceCash + buying power
kalshi://positionsOpen positions (unsettled)
kalshi://ordersResting orders (open / partially filled)

A WebSocket-backed live-orderbook resource (kalshi://markets/{ticker}/orderbook) is planned — for now, use the kalshi_get_live_orderbook tool which opens a transient WS, samples the book, and returns the current snapshot + delta arrival rate.

Safety model

This server is deliberately conservative for the same reason your bank's ATM is — small mistakes shouldn't cost large amounts.

  • KALSHI_ENV=prod requires KALSHI_ALLOW_PROD=1. The server refuses to start without both.
  • Placing orders requires KALSHI_TRADING_ENABLED=1. The default is read-only. Cancel and decrease stay available either way — they only reduce exposure, so an emergency cancel is never blocked.
  • Per-order caps (MCP_MAX_ORDER_SIZE_USD, MCP_DAILY_LIMIT_USD, MCP_MAX_CONTRACTS_PER_ORDER, MCP_CASH_RESERVE_USD) are checked before the request reaches Kalshi — at prepare as a fast-fail, and again at confirm against the limits in force then, where the order's cost is reserved against the daily cap before it is sent. Cost is worst-case: notional plus a conservative estimate of Kalshi's taker fee. A non-zero cash reserve is checked against a fresh balance read, and the order is refused if the balance can't be read.
  • If a confirm times out or fails ambiguously, the order may have been placed: its cost stays reserved and the same confirmation_id can be retried (it resends the same client_order_id). Check kalshi_get_orders before preparing a replacement.
  • Tighten limits at runtime, no redeploy. Those env vars are the hard ceiling. The kalshi_set_safety_limits tool can tighten any limit on a running server (e.g. a fast clamp-down). By default it can only tighten relative to the current value — the three caps only go down, the cash reserve only goes up — so a runtime caller can't undo a clamp-down. Relaxing a limit needs MCP_ALLOW_RUNTIME_LIMIT_LOOSENING=1, and even then never past its env ceiling. Raising a ceiling still requires changing the env var and redeploying (see AGENTS.md for undoing a persisted tighten). The limits in force vs. their ceilings show up in kalshi_get_environment and kalshi://environment. Set MCP_REDIS_URL to make runtime changes survive a restart (otherwise they reset to the env ceilings on reboot).

See AGENTS.md for the full design.

Deployment

Use it locally as a stdio server with any MCP client, or run it as a remote HTTP MCP behind an OAuth proxy.

For remote deployment, the recommended setup is image-deploy: a production host (Render, Fly.io, Cloud Run, ECS, anything that supports pulling container images) pulls the image that's built and pushed when you tag a release (git tag v0.1.0). This decouples deployments from PR merges — PRs to main only ever run tests, never push a new image — so a malicious or careless PR cannot affect what's running in your container.

See DEPLOY.md for the rationale and a worked example with Render.

Contributing

PRs welcome. Read CONTRIBUTING.md first — there are a few rules around auth changes, secret hygiene, and test conventions.

License

MIT. See also DISCLAIMER.md — the MIT license disclaims warranty; DISCLAIMER.md spells out the trading- and AI-specific risks you're accepting by using this software.

Acknowledgments

Frequently asked questions

What is Kalshi Prediction Markets?

MCP server for Kalshi prediction markets: native RSA-PSS auth, rate limiting, demo/prod safety.

How do I install Kalshi Prediction Markets?

It runs on your machine. In Claude Code: claude mcp add kalshi-prediction-markets -e 'KALSHI_API_KEY_ID=<KALSHI_API_KEY_ID>' -e 'KALSHI_PRIVATE_KEY_PATH=<KALSHI_PRIVATE_KEY_PATH>' -e 'KALSHI_PRIVATE_KEY_PEM=<your kalshi_private_key_pem>' -e 'KALSHI_ENV=<KALSHI_ENV>' -e 'KALSHI_ALLOW_PROD=<KALSHI_ALLOW_PROD>' -e 'KALSHI_TRADING_ENABLED=<KALSHI_TRADING_ENABLED>' -- uvx kalshi-mcp-server==0.1.8. For other clients, paste the JSON configuration from this page into their MCP settings.

Does Kalshi Prediction Markets need an API key?

Yes. It expects KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY_PEM, which you set in its configuration.

Is Kalshi Prediction Markets open source?

Yes. Its code is on GitHub under the MIT license.

Who maintains Kalshi Prediction Markets?

It is published by cejor6. AI.tn lists it but does not maintain or endorse it.

From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.