kalshi-readonly
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
kalshi-readonly
Kalshi Trade API v2 MCP over stdio (kalshi-mcp-v1-readonly-fleet). Read tools cover exchange status, markets, cash, positions, and a ranked list of small-stake ideas. Trade tools place, cancel, amend, or decrease one order. They are gated behind KALSHI_SAFE_MODE. They are not absent from v1.
Fleet default is KALSHI_SAFE_MODE=1. tools/list then omits place_order, cancel_order, amend_order, and decrease_order, and those handlers still refuse the call. The Finance Engineer harness (harness/SKILL.md) is the only policy that sets KALSHI_SAFE_MODE=0, and only on the Finance Engineer host. There is no separate Kalshi role harness. There is no deposit tool, no withdraw tool, and no auto-trading tool. find_best_bets never places an order.
Run
Python 3.11+ and cryptography==41.0.7 (see requirements.txt).
pip install -r requirements.txt
python3 server.pyserver.py speaks MCP on stdin/stdout and waits for a host. Do not type at it.
pip install -r requirements-dev.txt
pytestRelated MCP server: Kalshi MCP Server
Environment
Copy .env.example. Portfolio and order calls need a key id and a private key. Public market calls do not.
Variable | Required | Purpose |
| portfolio and trade calls | API key id. Or set |
| alternative to the key id | File containing the key id. If both id env vars are unset, |
| one of these | PEM file path. If PEM env vars are unset, |
| one of these | PEM contents |
| no | Default |
| no | Defaults to on. See Trade. |
Missing auth fails before any portfolio or order request. The private key is used only to sign. It is not logged and not returned. If a response or error would contain key material, that text is dropped.
Signing matches the cash CLI for GET, POST, and DELETE: timestamp_ms + METHOD + path (path includes /trade-api/v2, query string excluded, body excluded), RSA-PSS with SHA-256 and a digest-length salt, base64 signature. Headers are KALSHI-ACCESS-KEY, KALSHI-ACCESS-TIMESTAMP, and KALSHI-ACCESS-SIGNATURE.
Prove
No-network fleet check. The printed names must include exchange_status, cash_or_positions, find_best_bets, append_decision, summarize_decisions, and fe_routine, and must omit place_order, cancel_order, amend_order, and decrease_order.
KALSHI_SAFE_MODE=1 python3 -c 'from kalshi_readonly.tools import registered_tools; print([t["name"] for t in registered_tools()])'
printf '%s\n' '{"id":1,"op":"list"}' | KALSHI_SAFE_MODE=1 python3 -m kalshi_readonly.dispatchOn the box, after the host is up: call exchange_status, then cash_or_positions with include=both. The same cash read from the CLI, which does not place:
export KALSHI_API_KEY_ID="$(tr -d '[:space:]' < /path/to/key_id)"
export KALSHI_PRIVATE_KEY_PATH=/path/to/private.pem
python3 -c 'import json; from kalshi_readonly.tools import cash_or_positions; print(json.dumps(cash_or_positions({"include":"both","limit":5}), indent=2))'~/.secrets/kalshi/key_id and private.pem are enough when those env vars are unset. A host that says Not connected needs npm run build and a restart so node dist/index.js is the live process. Then tools/list is the check.
exchange_status includes build_sha. That value is the first 12 characters of <install root>/DEPLOYED_SHA, read once when the process starts. A missing or invalid file reports unknown.
Refresh
The box host tree is a git archive with no .git. Refresh it from a git checkout of this repo. The script writes DEPLOYED_SHA, then runs npm ci, npm run build, and python3 -m pytest -q. It does not restart the host and it does not call Kalshi. Restart the host after a successful run so the new process reads the stamp.
After scripts/refresh-host.sh, the stdio MCP has to be fully respawned: stop the old node and python processes, then reconnect. The python child's cwd otherwise follows the renamed .previous tree.
scripts/refresh-host.sh --help
scripts/refresh-host.sh --dry-run --dest /home/box/mcp/kalshi-mcp-main 1ce6d72
scripts/refresh-host.sh --dest /home/box/mcp/kalshi-mcp-main 1ce6d72A second run of the same commit prints already installed and does not rebuild. --dry-run resolves the commit and writes nothing. --dest must be an absolute path outside this checkout. An existing dest must be empty or already this install. A directory that contains .git is not this install. The script refuses any other directory.
Install as a Cursor plugin
Cursor hosts read .cursor-plugin/plugin.json (kalshi-readonly, version 0.3.0). Root plugin.json and mcp.json stay in place. Skills are under skills/. The Finance Engineer checklist is harness/SKILL.md. Stdio launch for the Cursor plugin is root mcp.json: command python3, args ${PLUGIN_ROOT}/server.py, env KALSHI_API_KEY_ID, KALSHI_PRIVATE_KEY_PATH, KALSHI_PRIVATE_KEY_PEM, and KALSHI_SAFE_MODE (default on). No secrets are committed.
This repository is not published to the Cursor Marketplace or cursor.directory. Publishing is out of scope. Until then, use the IDE mcp.json entry below, or the Grok Bot command in the next section.
server.py puts its own directory on sys.path before importing the package, so a host can launch it from another working directory. That includes Python's safe path (python3 -P). Do not paste the private key into chat. Do not set confirm unless Akash has confirmed that order.
Grok Bot
Use the Node entry. It speaks MCP stdio with @modelcontextprotocol/sdk (the same transport bambu-mcp uses) and keeps one Python worker for the tool calls.
npm install
npm run buildnode /absolute/path/to/kalshi-mcp/dist/index.jsSet these in the host env, not on the command line:
KALSHI_API_KEY_IDorKALSHI_API_KEY_ID_PATH(default~/.secrets/kalshi/key_id)KALSHI_PRIVATE_KEY_PATHorKALSHI_PRIVATE_KEY_PEM(default~/.secrets/kalshi/private.pem)KALSHI_SAFE_MODE=1on every fleet host
KALSHI_SAFE_MODE=1 is the fleet default. Missing or empty stays on. tools/list omits place_order, cancel_order, amend_order, and decrease_order. find_best_bets only reads. Do not commit the key file.
{
"mcpServers": {
"kalshi-readonly": {
"command": "node",
"args": ["/absolute/path/to/kalshi-mcp/dist/index.js"],
"env": {
"KALSHI_API_KEY_ID_PATH": "/absolute/path/to/.secrets/kalshi/key_id",
"KALSHI_PRIVATE_KEY_PATH": "/absolute/path/to/.secrets/kalshi/private.pem",
"KALSHI_SAFE_MODE": "1"
}
}
}
}Omit the path env vars when ~/.secrets/kalshi/key_id and private.pem already exist for that user. Do not commit those files.
Finance Engineer override, and only that host. harness/SKILL.md owns the decision. Do not add a second Kalshi role harness. On the box, use Refresh above instead of a hand-run npm install. Install, in order:
From a git checkout,
scripts/refresh-host.sh --dest /home/box/mcp/kalshi-mcp-main <sha>so that tree matches the commit.--dry-runprints the plan and writes nothing. A checkout you are editing still usesnpm run buildin that checkout.Point the host at
node /absolute/path/to/kalshi-mcp/dist/index.jswithKALSHI_SAFE_MODE=0.Restart the host. The Node process answers MCP
initializebefore the Python worker finishestools/list, thentools/listwaits for that list. The list is fixed for the life of the process (listChangedis false). A restart that leaves no livedist/index.jsshows up as Not connected. Rebuild and restart so the host spawns the process again.Prove the new process with
tools/list. It must includefe_routine,find_best_bets,place_order,cancel_order,amend_order, anddecrease_order. stderr from the entry includessafeMode=offand those names.exchange_statusis the cheap public call that the worker can reach Kalshi. It does not place.
The Finance Engineer sleeve sets KALSHI_SAFE_MODE=0 and restarts the host. place_order, cancel_order, amend_order, and decrease_order then show up. Every one of those calls still needs confirm: true. Opening risk still has to fit the caps: about $2 until Kalshi fill history shows the sleeve is profitable, hard max $15, at most 15% of the sleeve in one market, and at most 30% in one corr_group. There is no withdraw tool and no deposit tool. The daily and end-of-day prompt is fe_routine and harness/fe-grok-bot-routine.md.
{
"mcpServers": {
"kalshi-readonly": {
"command": "node",
"args": ["/absolute/path/to/kalshi-mcp/dist/index.js"],
"env": {
"KALSHI_API_KEY_ID": "your-key-id",
"KALSHI_PRIVATE_KEY_PATH": "/absolute/path/to/private.pem",
"KALSHI_SAFE_MODE": "0"
}
}
}
}"KALSHI_SAFE_MODE": "0" in the block above is the Finance Engineer host only. Every other host stays at "1". Restart after you change it so the tool list reloads. listChanged stays false for the life of the process.
Cursor
{
"mcpServers": {
"kalshi-readonly": {
"command": "python3",
"args": ["/absolute/path/to/kalshi-mcp/server.py"],
"env": {
"KALSHI_API_KEY_ID": "your-key-id",
"KALSHI_PRIVATE_KEY_PATH": "/absolute/path/to/private.pem",
"KALSHI_SAFE_MODE": "1"
}
}
}
}mcp.json in this repo is the plugin-host launch (python3 and ${PLUGIN_ROOT}/server.py). Grok Bot should use the Node command above instead.
Tools
Tool | What it does |
| Public exchange status, plus Washington region attestation ( |
| Public markets for liquid singles. Default |
| Validate one place, skip, or outcome and append it to the JSONL ledger. Does not trade. Does not rewrite history. |
| Week and life-to-date stats from that ledger only. Does not invent N |
| Read-only rank. See Bets. Does not place orders. Rows include |
| Daily and end-of-day prompt for the Finance Engineer. Does not trade |
| Cash and positions. |
| Resting orders only ( |
| One order. Hidden until safe mode is off. Requires |
| One cancel. Hidden until safe mode is off. Requires |
| Price and total fillable count. Hidden until safe mode is off. Requires |
| Reduce resting size. Hidden until safe mode is off. Requires |
cash_or_positions returns balance_cents, balance_dollars, and cash (the same official dollar string). portfolio_value is included only when the balance endpoint sends it. market_positions and event_positions each include ticker and qty when the API sends a quantity. Market side is the official sign of that quantity: positive YES, negative NO. avg and mark are copied only when the API sends them. include=fills reads recent fills (ticker, side, qty, official prices). This server does not compute remaining-to-recover.
Bets
find_best_bets is a read. The dry-run command is python3 -m kalshi_readonly.recommend --fixture tests/fixtures/recommend_scan.json. That command uses the file only. It does not call Kalshi and it does not place orders.
The score is estimated_confidence * payout_ratio / stake_needed. stake_needed is the ask for one contract. payout_ratio is the profit per dollar you stake if that side wins. A 15 cent contract with high confidence sorts ahead of an 82 cent contract, because the 82 cent contract ties up more cash for a smaller payout.
Pass beliefs when you can name the evidence: ticker, side (yes or no), confidence (0 to 1), and an optional evidence string. At most 20. Those tickers are one GET /markets?tickers=... request. The server does not fetch each ticker on its own, and it does not pull an order book per market. Without beliefs, you get a research_queue and no recommendations. A queue row is not a bet.
Defaults, all overridable inside a fixed range:
Knob | Default | Range |
| 0.55 | 0.50 to 0.99 |
| 0.08 | 0 to 0.90 |
| 0.84 | 0.05 to 0.84 |
| 5 | 1 to 25 |
| 20 contracts of 24h volume (weaker proxy) | 0 to 1000000 |
| 1000 contracts lifetime | 0 to 100000000 |
| 1 | 0 to 100000 |
| 2 | 0 to 168 |
| 1440 (60 days) | above the minimum, up to 8760 |
| 1 | 1 to 4 |
| 100 | 1 to 200 |
| 5 rows | 1 to 10 |
Prices at or above 0.85 are never recommended. The scan also skips multivariate combos (mve_filter=exclude) so a page is not spent on them. Public GET /markets counts against the same IP budget as signed calls, so the default scan is one page. A successful list is cached for about 5 minutes in the process. Concurrent scans of the same page share one in-flight request. Separate processes share markets-get.lock so those GETs start at least 1.5 seconds apart. The file is under KALSHI_STATE_DIR, or /tmp/kalshi-mcp when that variable is unset. Hosts that should share the limit need the same directory. The Node entry keeps that process alive across tool calls.
rate_limited is false on a normal read. If a later scan page still gets HTTP 429 or 503 after the retry budget, the tool returns the pages already fetched, sets rate_limited to true, and does not place. That partial list is reused for about 60 seconds. A rate limit on the first page fails the tool with an error that starts with rate_limited. Do not scan again immediately.
HTTP 429 and 503 are tried at most 5 times. A Retry-After value is honored up to 30 seconds. Without that header, or when it is not a delay or a date, the waits are 2s, 4s, 8s, then 16s, plus up to 0.25s of jitter. The next step of that schedule is capped at 30 seconds. That replaces the 0.25s, 0.5s, 1s, 2s schedule. The error is rate_limited only after that budget is spent.
fee_cents_est is the official schedule for the suggested role, looked up by series prefix before the score: taker ceil(M_taker * 0.07 * C * P * (1-P) * 100) cents, maker ceil(M_maker * 0.0175 * C * P * (1-P) * 100). At P=0.50, C=1, M_taker=1 the raw taker amount is 1.75 cents and the round-up is 2 cents. NFL, MLB, Fed, and CPI are usually M_taker=1 and M_maker=1. Weather (KXHIGH) is M_taker=1 and M_maker=0 by inference, because those series are absent from the Non-Standard table. Some crypto year-end series (KXBTCY, KXETHY) are 0/0. An unlisted series stays taker 1 and maker 0. A payload fee_multiplier (also series_fee_multiplier or fee_multiplier_fp) overrides both sides. edge_net_cents subtracts that taker dome, half the spread, and a depth haircut (0 when the ask shows at least 3 contracts, otherwise the shortfall capped at 2 cents). maker_flag is true when side_exec is maker. hold_to_res_default is true when days to resolution are at most 7 and the round-trip fee is larger than the remaining edge. There is no settlement fee and no fixed take-profit percent. wx_gap_pp is copied onto the row only when a belief passes it. It is not an entry gate.
Every response includes places_orders: false and the sentence Finance Engineer may place under the caps with confirm true. The tool still does not place. Each ranked row reports gates_passed and gates_failed (empty on a pass), plus edge_net_cents after the taker fee dome (ceil(M_taker * 0.07 * contracts * price * (1 - price)) cents), flb_band, kelly_frac of 0.25, stake_mode, side_exec / SIDE_EXEC, days_to_res / DAYS_TO_RES, spread_cents, depth_at_ask, fee_cents_est, m_taker, m_maker, fee_m_source, corr_group_hint, and hold_to_res_default / HOLD_TO_RES_DEFAULT. min_edge (default 0.08) is probability points and is separate from edge_net_cents. A row with edge_net_cents at or below 0 is dropped after the fee, the spread, and the depth haircut. A taker quote in the <10¢ band is dropped unless the belief sets allow_longshot true, edge_net_cents is at least 8, and the suggested stake is forced to $2. A 10–25¢ quote uses stake_mode fixed_2 and caps suggested_max_dollars_risked at $2. Lifetime volume of at least 1000 passes; 24h min_volume is the weaker proxy, and both numbers are on the row. A maker quote whose taker fee is within 2 cents sorts 0.0001 ahead of an equal score. The displayed score stays the formula. max_price defaults to 0.84, the hard ceiling, so a 50¢–84¢ contract with edge is eligible. The previous default of 0.50 hid that band; pass max_price 0.50 to restore it. The Finance Engineer routine is fe_routine and harness/fe-grok-bot-routine.md.
Decision ledger
append_decision and summarize_decisions are reads of local state. They stay on tools/list while KALSHI_SAFE_MODE=1. They do not place orders.
The ledger is append-only JSONL. A later settlement is a new line with action outcome and parent_id. The server never rewrites an earlier line. The latest outcome for a parent_id is the one that counts.
Plugin path: KALSHI_STATE_DIR (default ~/.local/state/kalshi) plus ledger/decisions.jsonl. KALSHI_LEDGER_PATH overrides that file. Finance Engineer mirrors it to /workspace/state/kalshi/ledger/decisions.jsonl. summarize_decisions regenerates stats-weekly-YYYY-Www.json and stats-ltd.json beside the ledger. Those two files are rewritten. The JSONL is not.
summarize_decisions reports n_places, n_skips, and n_resolved from the lines on disk. hit_rate is wins divided by wins plus losses, and it is null when that denominator is 0. roi_after_fees is realized pnl divided by parent stake, and it is null when stake is missing. Brier and log-score are null until 20 binary outcomes. N under 10 is counts and P&L only (claim_level counts_and_pnl_only). N under 30 labels Bürgi maker/taker and the fee dome as external priors and does not treat them as this sleeve's track record. Concurrent corr_group percent is null because sleeve cash is not on the ledger.
find_best_bets puts gates_passed and gates_failed on each recommendation. A scored skip is a row on skipped with failed_gate. The research queue is unchanged: no beliefs is not a decision tree.
Catalog size
KALSHI_SAFE_MODE=1 advertises 8 tools: exchange_status, list_markets, find_best_bets, append_decision, summarize_decisions, cash_or_positions, fe_routine, list_open_orders.
KALSHI_SAFE_MODE=0 adds place_order, cancel_order, amend_order, and decrease_order (12 tools). Mutations stay behind safe mode, the same as before. Fleet hosts stay at 8. The Finance Engineer host is the one that carries 12. A host that caps a server near 10 tools should keep safe mode on everywhere except that host.
Trade
Opening risk is capped in process. The server does not set confirm for you.
Cap | Default |
Stake | $2 until fill history shows the sleeve is profitable. Prices under 25 cents stay at $2 |
Hard max | $15 per trade. A setting above 15 is ignored |
One market | 15% of the $71 sleeve |
One corr_group | 30% of the sleeve. |
New notional per UTC day | $10 |
Cancels and decreases do not add risk and are not size-capped. An amend that would increase risk is capped. Size above the $2 default requires at least three closing trades and a positive net on this account.
KALSHI_SAFE_MODE defaults to on. Missing, empty, 1, true, yes, on, and any other value keep it on. Only 0, false, no, and off turn it off. While it is on:
place_order,cancel_order,amend_order, anddecrease_orderare omitted fromtools/list.Calling one of those handlers anyway returns an error and does not hit the network.
list_open_ordersstays registered. It is a signed GET of resting orders.
The Finance Engineer harness is the only unlock. After that host sets KALSHI_SAFE_MODE=0, restart it so tools/list reloads. Every mutating call still requires confirm to be the boolean true. false, "true", 1, and a missing confirm are refused before auth and before HTTP. The confirm flag is not copied into the Kalshi body. Fleet hosts do not set 0.
Tool | Kalshi route | Required arguments |
|
|
|
|
|
|
|
|
|
|
|
|
|
| none |
bid buys YES. ask sells YES. Prices are YES-book dollar strings strictly between 0 and 1, such as 0.4200. count is a contract string such as 1.00 (a positive integer is accepted). Amend count is already filled plus the desired resting remainder, not a reduce-by amount. Decreasing size keeps queue position. A price change or a larger size does not. Use decrease_order to reduce.
time_in_force is fill_or_kill, good_till_canceled, or immediate_or_cancel. The maker path is time_in_force=good_till_canceled and post_only=true. post_only with fill_or_kill or immediate_or_cancel is refused here. Kalshi also rejects a post_only order that would cross, so a maker flag cannot silently take. self_trade_prevention_type is taker_at_cross or maker. expiration_time is only valid with good_till_canceled. reduce_only is only valid with immediate_or_cancel and does not require corr_group. Pass client_order_id yourself when you want dedup. This server does not invent one.
A successful place_order echoes role (maker when post_only is true, otherwise taker), fee_cents_est from the series catalog on the ticker (unlisted maker M is 0; NFL, Fed, and CPI maker M is 1), flb_band for the entry price (price on a bid, 1 - price on an ask), edge_net_cents when the caller passes it (the server does not invent one), corr_group, order_id, and day_spend_remaining from the live book against the soft $10 UTC-day cap. fe_routine does not invent that remaining figure. Size above $2 still needs a profitable Kalshi fill history. If the ack already shows a fill, one fill line is appended to the decision ledger for that audit. The line does not raise the $2 cap. There is no withdraw and no deposit.
Optional place fields: client_order_id, expiration_time, post_only, reduce_only, cancel_order_on_pause, subaccount, order_group_id, exchange_index.
Demo host, if you use one: KALSHI_API_BASE=https://demo-api.kalshi.co/trade-api/v2.
There is no batch place, no batch cancel, no withdraw, and no deposit.
This server cannot be deployed
Maintenance
Related MCP Connectors
Read-only access to your own TTMT trading data: trades, signals, channels, performance and risk.
Hosted MCP for Kalshi prediction markets: search, odds, order books, settlement rules, and trading.
Trades, stats, playbooks and notes from your Trandence trading journal. Read-only by default.
Read-only market charts, OHLC, backtests and a view of your connected broker accounts
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables interaction with the Kalshi prediction market platform through their REST API. Supports trading and market operations with secure RSA key authentication for both production and demo environments.32MIT
- AlicenseNot gradedqualityCmaintenanceEnables users to search, analyze, and trade on Kalshi prediction markets directly through MCP-compatible clients. It supports portfolio management, market data retrieval, and limit order execution with production-grade authentication and rate limiting.20 PyPI1MIT
- AlicenseNot gradedqualityBmaintenanceProvides access to US-regulated prediction-market data from Kalshi, enabling queries on market events without authentication.321 npmMIT
- AlicenseBqualityDmaintenanceEnables AI assistants to trade on Kalshi through natural language, including placing orders, market data access, portfolio management, and paper trading with safety guardrails.608 npm3MIT