magicmarkets-cli
MCP serverAI & modelsSports prediction markets for AI agents, live prices, quotes, orders, and positions.
Use magicmarkets-cli in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add magicmarkets-cli and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use magicmarkets-cli
Needs your own magicmarkets-cli account. You sign in to it and approve access when you connect.
Details
Available today. Use it from your connected AI after setup.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Install magicmarkets-cli
The server’s own address, for the clients that take one directly. Or connect ahel once and every client you use reads it from one address, with the account kept on ahel rather than in each client’s config.
Claude Code
claude mcp add --transport http --scope user magicmarkets-cli 'https://magicmarkets.com/mcp'Run it once in your project, then open /mcp to approve any sign-in the server asks for.
Claude Desktop
https://magicmarkets.com/mcpAdd a custom connector in Settings, paste this address, and approve the sign-in.
Cursor
cursor://anysphere.cursor-deeplink/mcp/install?name=magicmarkets-cli&config=eyJ1cmwiOiJodHRwczovL21hZ2ljbWFya2V0cy5jb20vbWNwIn0=Open the link and Cursor adds the server at that address.
ChatGPT
https://magicmarkets.com/mcpIn Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.
Codex
codex mcp add magicmarkets-cli --url 'https://magicmarkets.com/mcp'Run it once, then sign in with codex mcp login magicmarkets-cli if the server asks for an account.
From the project's README
As published by magicmarkets/magicmarkets-cli in README.md.
A command-line interface for the Magic Markets v2 API — stream live sports prices, quote selections, place and manage orders, and inspect your position. The same API is also available as MCP tools, over stdio (a client launches magicmarkets mcp as a subprocess) or the streamable HTTP transport on localhost.
Single static binary, authenticated with one API key. No request signing, no private keys.
magicmarkets markets --sport fb # find events with live prices
magicmarkets offers fb 2026-06-15,1001,2002 # list priced bet types
magicmarkets betslip create fb 2026-06-15,1001,2002 for,h --wait 5s
magicmarkets order place --betslip bs-123 --price 2.10 --stake 50
- Setup — install, authenticate, first commands
- Using it — the bet flow, command reference, MCP, prices, errors
- Development — layout, code generation, conventions, contributing
Setup
1. Install
From a clone:
git clone https://github.com/magicmarkets/magicmarkets-cli
cd magicmarkets-cli
make install # installs `magicmarkets` into your Go bin directory
make where prints exactly where that landed. If magicmarkets is not found afterwards, that directory is not on your PATH:
export PATH="$PATH:$(go env GOPATH)/bin" # add to ~/.zshrc or ~/.bashrc
Prefer not to install? make build produces ./build/magicmarkets and leaves your PATH alone.
Note the /cmd/magicmarkets path — installing the module root would build a binary called magicmarkets-cli:
go install ./cmd/magicmarkets
The Go module path is magicmarkets-cli, not a GitHub URL, so go install github.com/…/magicmarkets-cli@latest will not work. Cloning and make install is the supported path.
2. Add your API key
Create a key at magicmarkets.com under Settings → API. It is shown once at creation, so store it immediately.
Put it in ~/.magicmarkets/.env so it works from any directory:
mkdir -p ~/.magicmarkets
echo 'MAGICMARKETS_API_KEY=your-key-here' > ~/.magicmarkets/.env
chmod 600 ~/.magicmarkets/.env
An env var or a project-local .env works too — cp env.example .env and fill it in.
3. Verify
$ magicmarkets status
version v1.0.0
api url https://magicmarkets.com/v2
ws url wss://magicmarkets.com/v2/stream
lang en
api key ***********1234
env files [/Users/you/.magicmarkets/.env]
authenticated yes
Always check this before opening a stream — the WebSocket rejects a bad key at the handshake without a useful error.
That's the whole setup. Everything below is optional.
Try it
magicmarkets balance # money position
magicmarkets xrates # exchange rates to USDT
magicmarkets markets --sport fb --limit 5 # events that currently have prices
magicmarkets offers fb <event-id> --depth 2 # priced bet types on one event
magicmarkets orders --open # your open orders
magicmarkets ticks 2.345 # where a price lands on the tick schedule
magicmarkets api endpoints # every endpoint (no key, no network)
Add --json to any command to pipe it into jq.
Configuration
Resolved in this order, first match winning:
| Priority | Source |
|---|---|
| 1 | Real environment variables |
| 2 | ./.env |
| 3 | ~/.magicmarkets/.env |
| 4 | ~/.env |
| 5 | Built-in defaults |
| Variable | Default | Purpose |
|---|---|---|
MAGICMARKETS_API_KEY | — | API key (X-Api-Key). Required unless MAGICMARKETS_ACCESS_TOKEN is set |
MAGICMARKETS_ACCESS_TOKEN | — | OAuth Bearer token for CLI/stdio. HTTP MCP still takes the token per request |
MAGICMARKETS_OAUTH_ISSUER | https://magicmarkets.com/api/auth | Upstream AS that mcp --http proxies /authorize and /token to, and that POST /oauth2/firebase-token is resolved against for a Bearer credential — see Authentication |
MAGICMARKETS_SESSION_GROUP_ID | — | Environment-specific id from Magic Markets, used to build the session value an OAuth Bearer credential resolves to. No safe default — required wherever a Bearer credential is expected |
MAGICMARKETS_FIREBASE_WEB_API_KEY | — | Firebase Web API key for the Magic Markets Firebase project, used to exchange the Firebase custom token from /oauth2/firebase-token for a real Firebase ID token — see Authentication. No safe default — required wherever a Bearer credential is expected |
MAGICMARKETS_OAUTH_CLIENT_ID | — | Pre-registered client allowlisting this host's own /mcp/callback |
MAGICMARKETS_OAUTH_PROXY_SECRET | — | Seals OAuth proxy state; every replica must share it |
MAGICMARKETS_MCP_PUBLIC_URL | — | Public base of mcp --http (this host is the MCP Authorization Server) |
MAGICMARKETS_API_URL | https://magicmarkets.com/v2 | REST base, including /v2 |
MAGICMARKETS_BASIC_AUTH | — | Base64 user:pass sent as an additional Authorization: Basic header on every /v2/* REST call — the token for an infra-level wall some non-production environments put in front of the v2 API. Production has no such wall, so this is unset there. Never sent to /oauth2/firebase-token, Firebase's signInWithCustomToken, or the WebSocket stream |
MAGICMARKETS_WS_URL | derived from MAGICMARKETS_API_URL | Stream endpoint |
MAGICMARKETS_LANG | en | Event name language: en, ko, zh-hans |
MAGICMARKETS_TIMEOUT | 30s | Per-request timeout |
MAGICMARKETS_ALLOW_TRADING | unset (off) | Lets magicmarkets mcp place bets. No effect on the CLI. |
Global flags: --json, --verbose/-v, --api-key, --api-url, --ws-url.
--api-url re-derives the stream endpoint from it (matching MAGICMARKETS_WS_URL's
own derivation), unless MAGICMARKETS_WS_URL or --ws-url pins it explicitly —
so --api-url https://staging... doesn't leave stream reading production
prices while every other command reaches staging.
Using it
The two-step bet flow
Placing a bet always takes two steps:
- A betslip registers interest in one selection and receives a live quote. It costs nothing and commits nothing.
- An order commits a stake against that quote.
Betslips are short-lived and carry no price when created — the quote arrives asynchronously, over the WebSocket as a pmm message or by polling. Hence --wait:
# 1. find an event that has prices
$ magicmarkets markets --sport fb --limit 5
SPORT EVENT ID EVENT COMPETITION STATUS START
----- -------- ----- ----------- ------ -----
fb 2026-06-15,1001,2002 Arsenal v Chelsea England Premier League pre_event 2026-06-15 16:00:00
# 2. read a bet_type straight off the feed
$ magicmarkets offers fb 2026-06-15,1001,2002 --depth 2
BET TYPE MARKET IR PRICES (stake @ price) TOTAL
-------- ------ -- ---------------------- -----
for,h 1x2 - 150.00@2.10 80.00@2.08 230.00
for,ah,h,-4 ah - 200.00@1.95 120.00@1.94 320.00
# 3. quote it, waiting for the price to land
$ magicmarkets betslip create fb 2026-06-15,1001,2002 for,h --wait 5s
betslip id bs-abc123
bet type for,h
description Home
expires 2026-06-15 15:42:10 (28s)
total available 230.00 USDT
Prices:
PRICE MIN MAX
----- --- ---
2.10 5.00 150.00
2.08 - 80.00
# 4. commit a stake (asks for confirmation)
$ magicmarkets order place --betslip bs-abc123 --price 2.10 --stake 50
Never construct a bet_type by hand. Copy it verbatim from magicmarkets offers or the stream — it encodes the market, handicap, outcome and direction in one string.
Command reference
Account
| Command | Purpose |
|---|---|
magicmarkets status | Show config and verify the key |
magicmarkets balance | Balance, open stake, smart credit, available |
magicmarkets xrates | Exchange rates to USDT |
magicmarkets position | Aggregate P&L, with --grid for the payoff matrix |
Discovery
| Command | Purpose |
|---|---|
magicmarkets markets | Events that currently have prices |
magicmarkets offers <sport> <event-id> | Priced bet types on an event |
magicmarkets stream | Tail the live price and account feed |
The v2 REST API has no event-listing endpoint — discovery happens over the WebSocket. These commands connect, read the snapshot, and disconnect, so they take a few seconds.
magicmarkets markets --sport fb,tennis --limit 20
magicmarkets markets --search arsenal
magicmarkets markets --in-play
magicmarkets offers fb 2026-06-15,1001,2002 --market ah --depth 3
magicmarkets stream --register fb:2026-06-15,1001,2002
magicmarkets stream --type order,bet # only order activity
Trading
| Command | Purpose |
|---|---|
magicmarkets betslip create [sport] [event] [bet-type] | Quote a selection |
magicmarkets betslip get <id> | Show a betslip and its prices |
magicmarkets betslip list | Open betslip IDs (--expand for full detail) |
magicmarkets betslip refresh <id> | Extend expiry |
magicmarkets order place | Place an order against a betslip |
magicmarkets orders / magicmarkets order list | List orders |
magicmarkets order get <id> | Show one order and its bets |
magicmarkets order tracked <uuid> | Look an order up by idempotency key |
magicmarkets order updates | Orders changed in a time window |
magicmarkets order close <id> | Cancel one order |
magicmarkets order close-many <id>... | Cancel up to 500 orders |
magicmarkets order close-all | Cancel every open order |
Lay bets and parlays:
# lay (against) a selection
magicmarkets betslip create --lay fb 2026-06-15,1001,2002 for,over,2.5
# a 2-leg accumulator
magicmarkets betslip create \
--leg fb:2026-06-15,1001,2002:for,h \
--leg fb:2026-06-16,1003,2004:for,over,2.5 --wait 5s
Risk
| Command | Purpose |
|---|---|
magicmarkets heartbeat run | Create a heartbeat and keep it alive in the foreground |
magicmarkets heartbeat create/list/get/refresh/cancel | Manage heartbeats directly |
A heartbeat is a dead-man's switch: if it is not refreshed before it expires, every open order is closed automatically. Run one alongside an automated strategy so a crash cannot leave orders live.
$ magicmarkets heartbeat run --timeout 60
heartbeat hb-xyz created, expires 2026-06-15 15:43:10; refreshing every 20s
press Ctrl-C to cancel it and leave orders open
On Ctrl-C the heartbeat is cancelled cleanly, leaving orders open. If the process dies, the switch fires.
MCP
| Command | Purpose |
|---|---|
magicmarkets mcp | MCP tools over stdio by default, or --http for the streamable HTTP transport on localhost. See MCP |
Reference
| Command | Purpose |
|---|---|
magicmarkets bet-type <sport> <bet-type> | Validate a bet type, show its payoff grid |
magicmarkets ticks <price> | Snap a price onto the tick schedule |
magicmarkets api endpoints | List every endpoint |
magicmarkets api show <path> [method] | Endpoint detail: parameters, body, responses |
magicmarkets api schema [name] | Component schemas |
magicmarkets api curl <method> <path> | Generate a runnable curl command |
magicmarkets api search <term> | Search endpoints and schemas |
magicmarkets api spec | Print the embedded OpenAPI spec |
The magicmarkets api commands need no API key and no network — the OpenAPI 3.1 spec is compiled into the binary.
Safe-operation checklist
Worth internalising before running anything that spends money:
- Pass
--request-uuidon every order. It makes placement idempotent: a retry after a timeout cannot create a second order, and the order stays retrievable for six hours. Without it, a timeout leaves you unsure whether a bet was placed. - Run a heartbeat when automating. Without one, a crashed strategy leaves orders live in the market.
- Verify the key over REST before opening a stream. The WebSocket fails the handshake with no useful error.
- Take
bet_typefrom the feed, never by hand. Asian handicap lines are 4× the real line, so a hand-built string is easy to get silently wrong. - Check the snapped price.
magicmarkets order placeshows it in the confirmation; that is the price the order actually runs with, not what you typed.
Prices and the tick schedule
Every price lies on a fixed tick schedule whose step widens as the price grows:
| Decimal price | Tick |
|---|---|
| 1.01 – 2 | 0.01 |
| 2 – 3 | 0.02 |
| 3 – 4 | 0.05 |
| 4 – 6 | 0.10 |
| 6 – 10 | 0.20 |
| 10 – 20 | 0.50 |
| 20 – 30 | 1 |
| 30 – 50 | 2 |
| 50 – 100 | 5 |
| 100 – 1000 | 10 |
An off-tick order price is rounded so it never tightens your limit: down for back (for) orders, up for lay (against) orders. magicmarkets order place snaps the price itself and shows the result in the confirmation.
$ magicmarkets ticks 2.345
snapped price 2.34 # back: rounded down
$ magicmarkets ticks 2.345 --lay
snapped price 2.36 # lay: rounded up
Prices quoted from the feed are already on the schedule and are never re-rounded.
Bet type grammar
bet_type is a comma-separated string beginning with the direction: for to back, against to lay. Handicaps always refer to the home team.
| Example | Meaning |
|---|---|
for,h / for,d / for,a | Home / draw / away win |
for,dnb,h | Home win, void if draw |
for,dc,h,d | Double chance: home or draw |
for,over,2.5 / for,under,2.5 | Over/under 2.5 goals |
for,ah,h,-4 | Asian handicap, home -1.0 |
for,ahover,7 | Asian total over 1.75 |
for,cs,2,1 | Correct score 2–1 |
for,score,both,yes | Both teams to score |
for,win,<team_id> | Runner to win an outright |
for,top,3,<team_id> | Runner to finish top 3 |
Asian handicap lines are integers equal to 4× the real line — -4 is -1.0, 2 is +0.5, 7 is +1.75. This keeps 0.25-step lines integer-only on the wire.
Validate any candidate string:
$ magicmarkets bet-type fb for,ah,h,-4
description Home -1.0 (Asian)
valid yes
The full grammar — tennis periods, time-period tokens, every market — is in docs/api-reference.md.
JSON output
Every command takes --json:
magicmarkets orders --open --json | jq -r '.[] | "\(.order_id) \(.status)"'
magicmarkets markets --sport fb --json | jq -r '.[].event_id'
magicmarkets stream --type order --json # one JSON object per line
Stakes are two-element tuples, not objects — --json mirrors the API wire
format exactly, so index them rather than reaching for a field name:
$ magicmarkets balance --json
{
"balance": ["USDT", 10000.5],
"open_stake": ["USDT", 152.55],
"smart_credit": null
}
$ magicmarkets balance --json | jq '.balance[1]' # amount
10000.5
$ magicmarkets balance --json | jq -r '.balance[0]' # currency
USDT
The same applies to every money field: want_stake, stake, profit_loss, total, and the min/max inside a price level.
MCP
magicmarkets mcp serves the API as MCP tools, so an LLM agent can read prices and manage orders. By default it speaks stdio: an MCP client (Claude Code, Cursor, and the like) launches it as a subprocess and they exchange JSON-RPC on stdin/stdout. Pass --http to instead serve the MCP streamable HTTP transport on localhost — see Serving over localhost HTTP below.
stdio
Register the command with a client:
claude mcp add magicmarkets -e MAGICMARKETS_API_KEY=your-key -- magicmarkets mcp
Or in a client's MCP config — mcpServers is the client's name for a stdio subprocess, not a network service:
{
"mcpServers": {
"magicmarkets": {
"command": "magicmarkets",
"args": ["mcp"],
"env": { "MAGICMARKETS_API_KEY": "your-key" }
}
}
}
MCP support is developer-mode for now — expect some friction wiring a stdio server into a given client, and expect that to keep improving. For client-specific setup (where the config file lives, restart behavior, log locations), see that client's own docs rather than this README:
- Claude Code: Connect to tools via MCP
- Model Context Protocol: Connect to local (stdio) servers — covers Claude Desktop and other MCP clients generically
Across clients, the most common snag is the command field: a client often spawns the subprocess with a minimal PATH, not your shell's, so a bare "command": "magicmarkets" can fail to resolve even though the same command works from a terminal. If that happens, swap in the absolute path instead:
which magicmarkets # or: make where
Serving over localhost HTTP
Pass --http to serve the streamable HTTP transport instead of stdio — useful when a client connects over the network, or you want one long-running server shared by several clients instead of a subprocess per client:
magicmarkets mcp --http --addr 127.0.0.1:8383
--addr defaults to 127.0.0.1:8383 — loopback-only, so nothing outside the machine can reach it regardless. --http has no TLS of its own — put it behind a reverse proxy if you expose it beyond loopback.
The server does not use MAGICMARKETS_API_KEY. Each request must send the caller's credential as X-Api-Key or Authorization: Bearer. An API key is forwarded to the Magic Markets REST API and /v2/stream unchanged. A Bearer token is not — it's resolved first, via POST {MAGICMARKETS_OAUTH_ISSUER}/oauth2/firebase-token, to the credential those actually require; see Authentication for the full flow and its known limitations. Stdio still takes MAGICMARKETS_API_KEY or MAGICMARKETS_ACCESS_TOKEN from the environment.
Point a client at the URL instead of a command:
{
"mcpServers": {
"magicmarkets": {
"url": "http://127.0.0.1:8383/mcp",
"headers": { "X-Api-Key": "your-key" }
}
}
}
The same URL accepts an OAuth access token:
{
"mcpServers": {
"magicmarkets": {
"url": "http://127.0.0.1:8383/mcp",
"headers": { "Authorization": "Bearer your-token" }
}
}
}
Remote MCP hosts that speak OAuth can skip the headers map. Unauthenticated /mcp replies include WWW-Authenticate pointing at protected-resource metadata that names this MCP host as the Authorization Server (so Claude's Dynamic Client Registration POSTs /register here, not https://magicmarkets.com/register). This process runs its own PKCE login against https://magicmarkets.com/api/auth — it never forwards a downstream client's redirect_uri upstream, since the real Magic Markets AS only allowlists this host's own callback ({public-url}/mcp/callback), never Claude's or Cursor's. Set:
--public-url https://magicmarkets-mcp.dev-eu.kubershmuber.comon the hosted deployMAGICMARKETS_OAUTH_CLIENT_IDto a client onMAGICMARKETS_OAUTH_ISSUERwhose redirect_uris allowlist includes that host's/mcp/callbackMAGICMARKETS_OAUTH_PROXY_SECRETon every replica of a multi-replica deployment — the proxy keeps no server-side session state (login state and one-time codes are sealed, self-contained tokens instead), so replicas that don't share this secret can't decode each other's in-flight logins
Enabling trading
Trading is off by default. A fresh registration is read-only, so an agent asking to place a bet will find no place_order tool at all. Enable it with MAGICMARKETS_ALLOW_TRADING=1, replace the existing registration, and restart your client:
claude mcp remove magicmarkets
claude mcp add magicmarkets -e MAGICMARKETS_API_KEY=your-key -e MAGICMARKETS_ALLOW_TRADING=1 -- magicmarkets mcp
Restarting matters: a client reads the subprocess's tool list once at startup, so an already-running session keeps the read-only list even after you re-register.
Editing a client's MCP config file directly, set MAGICMARKETS_ALLOW_TRADING in env:
{
"mcpServers": {
"magicmarkets": {
"command": "magicmarkets",
"args": ["mcp"],
"env": {
"MAGICMARKETS_API_KEY": "your-key",
"MAGICMARKETS_ALLOW_TRADING": "1"
}
}
}
}
Confirm which mode you are in without involving a client:
$ MAGICMARKETS_ALLOW_TRADING=1 magicmarkets mcp --print-tools
mode: trading ENABLED (via MAGICMARKETS_ALLOW_TRADING) — this process can place real bets
TOOL
----
close_all_orders
close_order
create_betslip
place_order
...
Run it without the env var to see the read-only list (11 tools vs 19). magicmarkets mcp also logs the mode to stderr on every start, which appears in your client's MCP logs (stderr is the only place those lines can go — stdout is the JSON-RPC stream).
What each mode exposes
| Always available | Requires MAGICMARKETS_ALLOW_TRADING |
|---|---|
get_balance, get_exchange_rates, get_position | create_betslip |
list_events, list_event_offers | place_order |
list_orders, get_order | close_order, close_all_orders |
list_betslips, get_betslip | create_heartbeat, refresh_heartbeat, cancel_heartbeat, list_heartbeats |
validate_bet_type, snap_price |
Enable trading only if the agent should be able to bet real money. The money-spending tools carry MCP destructive hints so clients prompt before calling them.
Note this gate applies to magicmarkets mcp only. The CLI's own magicmarkets order place is always available — it has its own confirmation prompt instead.
Errors
Errors carry a stable machine-readable code to branch on:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | validation_error | Body or query failed validation; per-field reasons are printed |
| 400 | order_closed | Order exists but is already closed or settled |
| 401 | auth_error | Key missing, malformed or rejected |
| 403 | forbidden | Key valid but action not allowed |
| 404 | not_found | Resource unknown or invisible to this key |
| 409 | order_already_created | A request_uuid was reused; the existing order ID is reported |
| 409 | limit_reached | A per-customer cap was hit |
| 429 | throttled | Rate limited; retried automatically, honouring Retry-After |
| 500 | server_error | Internal error; quote the support token when reporting |
Throttled requests are retried automatically (twice by default) because a 429 means the request was rejected outright, so nothing was created. No other status is retried.
Idempotency
uuid=$(uuidgen)
magicmarkets order place --betslip bs-123 --price 2.10 --stake 50 --request-uuid "$uuid"
magicmarkets order tracked "$uuid" # recover after a timeout, safely
If a reused UUID is detected, magicmarkets fetches and shows the original order instead of failing.
Rate limits
Per account, sliding window: 100 req/s burst and 1200 req/min sustained overall, with dedicated budgets of 10 req/s for betslip creation and 5 req/s for order placement.
Troubleshooting
no API key configured — set MAGICMARKETS_API_KEY. magicmarkets status shows which .env files were read.
auth_error (401) — no key was sent at all. Check the variable name.
session_not_found (404) on every call — the key was sent but is not recognised. Regenerate it under Settings → API.
Shortened here. Read the whole README on GitHub.
Signals
- GitHub stars
- 1
- Last commit
- Sep 2026
Advanced
- Delivery
- MagicMarkets MCP server → your ahel connector (mcp.ahel.ai) → your AI.
- Item type
- mcp-server
- Key
com-magicmarkets-mcp- Source
- github.com/magicmarkets/magicmarkets-cli
- Hosted endpoint
https://magicmarkets.com/mcp
github.com/magicmarkets/magicmarkets-cli
More in AI & models
MCP server · upstash
More in AI & modelsAdButler
MCP server · adbutler
More in AI & modelsatom-mcp-server
MCP server · a7om-ai
More in AI & modelsottasia
MCP server · aiweather-anurag
More in AI & modelsunphurl
MCP server · 123ergo
More in AI & modelsmicrosoft-learn-mcp
MCP server · microsoftdocs
More in AI & models