Tastytrade
FreeNot checkedRead-only MCP server for tastytrade account data, market metrics, historical candles, backtesting, and trade simulation.
About
Read-only MCP server for tastytrade account data, market metrics, historical candles, backtesting, and trade simulation.
README
A lightweight, read-only Model Context Protocol (MCP) server for tastytrade brokerage account data.
The server exposes account discovery, balances, positions, live orders, order search, transactions, fresh market data by product type, Market Metrics volatility and liquidity data, historical OHLCV candles (equities, equity/index options, indexes, and crypto), and tastytrade's own options-strategy backtesting and trade-simulation service. It intentionally does not submit, replace, or cancel live orders -- backtesting is the one deliberate exception, since it only ever simulates trades against historical data on tastytrade's own servers (see Backtesting below).
Requirements
- Python 3.10+
uvor another Python package manager- tastytrade sandbox or production API credentials
Setup
Linux/macOS:
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/roymeshulam/tastytrade-mcp-server.git
cd tastytrade-mcp-server
uv sync
cp .env.example .env
chmod 600 .env
Windows PowerShell:
uv venv
uv pip install -e ".[dev]"
cp .env.example .env
Set your .env values:
TASTYTRADE_ENV=production
REFRESH_TOKEN=your_refresh_token_here
CLIENT_SECRET=your_client_secret_here
DEFAULT_ACCOUNT_NUMBER=your_account_number_here
The account tools default to DEFAULT_ACCOUNT_NUMBER, but each
account-specific tool also accepts an explicit account_number argument.
TASTYTRADE_SESSION_TOKEN or TASTYTRADE_USERNAME/TASTYTRADE_PASSWORD can
still be used as fallback auth options for sandbox/dev workflows.
Run Locally
For a local MCP client that launches the server over stdio:
uv run tastytrade-mcp-server
For a long-running local HTTP MCP server on port 8010, set:
MCP_TRANSPORT=streamable-http
MCP_HOST=127.0.0.1
MCP_PORT=8010
MCP_STREAMABLE_HTTP_PATH=/mcp
MCP_ALLOWED_HOSTS=127.0.0.1:8010,localhost:8010
MCP_ALLOWED_ORIGINS=http://127.0.0.1:8010,http://localhost:8010
Then run:
uv run tastytrade-mcp-server
The local endpoint is:
http://127.0.0.1:8010/mcp
The root path / is not a web UI, so 404 Not Found there is expected.
Test With MCP Inspector
Run Inspector in a second terminal:
npx @modelcontextprotocol/inspector
Open the URL printed by Inspector and connect with:
Transport: Streamable HTTP
URL: http://127.0.0.1:8010/mcp
If Inspector asks for proxy authentication, use the session token printed in
the Inspector terminal. Test list_accounts first, then account-specific tools.
Public HTTPS Deployment
Remote clients such as mobile ChatGPT should use a public HTTPS URL, preferably on a domain name:
https://tastytrade.roymeshulam.com/mcp
- Point DNS at the server:
Host: mcp
Type: A
Value: <server-public-ip>
- Keep the MCP server bound to localhost in
.env:
MCP_TRANSPORT=streamable-http
MCP_HOST=127.0.0.1
MCP_PORT=8010
MCP_STREAMABLE_HTTP_PATH=/mcp
MCP_ALLOWED_HOSTS=tastytrade.roymeshulam.com
MCP_ALLOWED_ORIGINS=https://tastytrade.roymeshulam.com
Add https://chatgpt.com and https://chat.openai.com to
MCP_ALLOWED_ORIGINS if your MCP client sends those origins.
- Install Caddy on Ubuntu from the official Caddy apt repository:
sudo apt install -y \
debian-keyring \
debian-archive-keyring \
apt-transport-https \
curl \
gpg
curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/gpg.key \
| sudo gpg --dearmor \
-o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
- Configure Caddy in
/etc/caddy/Caddyfile:
tastytrade.roymeshulam.com {
reverse_proxy 127.0.0.1:8010
}
Then reload Caddy:
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
- Run the MCP server persistently with systemd. Example for a checkout in
/home/meshulro/Projects/tastytrade-mcp-server:
[Unit]
Description=tastytrade MCP Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=meshulro
Group=meshulro
WorkingDirectory=/home/meshulro/Projects/tastytrade-mcp-server
Environment=PYTHONUNBUFFERED=1
ExecStart=/home/meshulro/.local/bin/uv run tastytrade-mcp-server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Save that as /etc/systemd/system/tastytrade-mcp.service, then run:
sudo systemctl daemon-reload
sudo systemctl enable --now tastytrade-mcp
sudo systemctl status tastytrade-mcp
- Verify:
dig tastytrade.roymeshulam.com +short
systemctl is-active caddy
systemctl is-active tastytrade-mcp
curl -i -H 'Accept: text/event-stream' https://tastytrade.roymeshulam.com/mcp
For a raw curl request, 400 Missing session ID from the MCP server is a useful
sign that HTTPS and proxying work. A real MCP client will establish the session.
Plain HTTP on a raw IP address, such as http://176.57.150.218:8080/mcp, can be
useful for temporary testing but should not be used for brokerage account data.
Most public clients also expect trusted HTTPS certificates, which normally
requires a domain name rather than a bare IP address.
Authentication Note
This server currently authenticates to tastytrade using credentials in .env,
but it does not authenticate MCP clients by itself. Caddy basic_auth can
protect browser or Inspector testing, but ChatGPT remote MCP setup expects an
OAuth-compatible flow, not Basic Auth. Do not leave a public brokerage-data MCP
endpoint exposed without an access-control layer suitable for your client.
For more deployment detail, see docs/deployment.md.
For MCP clients that launch this process over stdio, adapt docs/mcp-client-config.example.json.
Tools
list_accountsget_account_balanceget_account_positionsget_live_orderssearch_ordersget_account_transactionsget_market_dataget_market_metricsget_historical_candleslist_backtest_symbolsrun_backtestget_backtest_resultlist_backtestscancel_backtestget_backtest_logssimulate_option_trade
get_market_data calls tastytrade's /market-data/by-type endpoint. Pass the
API product type and symbol, for example:
product_type=index
symbol=SPX
For equity options (which also covers index options like SPX/SPXW -- tastytrade has no separate "index-option" product type), you can either pass the API-ready padded OCC-style symbol directly:
product_type=equity-option
symbol=SPXW 260727P07250000
or pass the underlying as symbol plus expiration_date/option_type/
strike_price and let the server build the padded symbol for you -- this is
the friendlier option for an agent responding to a natural-language request
like "what's SPY's 560 put expiring 2026-09-18 trading at":
product_type=equity-option
symbol=SPY
expiration_date=2026-09-18
option_type=put
strike_price=560
get_market_metrics calls tastytrade's /market-metrics endpoint. Use it when
you need underlying-level volatility, liquidity, beta/correlation, dividend,
borrow, market cap, earnings, or per-expiration option implied volatility data.
Pass one or more underlying symbols as a JSON array:
symbols=["SPX"]
Multiple symbols are supported:
symbols=["AAPL", "FB", "BRK/B"]
The response is the raw tastytrade payload. Typical data.items fields include
symbol, implied-volatility-index, implied-volatility-index-5-day-change,
implied-volatility-index-rank, implied-volatility-percentile,
liquidity-value, liquidity-rank, liquidity-rating, beta,
corr-spy-3month, dividend fields, borrow fields, market-cap, earnings
fields, and option-expiration-implied-volatilities.
get_historical_candles fetches historical OHLCV bars from tastytrade's DxLink
streaming market data service (dxfeed). Unlike the other tools, this makes a
short-lived outbound WebSocket connection (see Historical candles
architecture below) rather than a plain REST
call; it still opens, fetches one bounded historical range, and closes for each
tool call.
product_type=equity
symbol=SPY
interval=1h
start_time=2026-08-01T13:30:00Z
end_time=2026-08-04T20:00:00Z
Parameters:
product_type— one ofequity,equity-option,future,future-option,cryptocurrency,index(same enum asget_market_data), but onlyequity,equity-option,index, andcryptocurrencyare currently supported for candles.futureandfuture-optionare rejected with a clear error — see the known limitation noted in Historical candles architecture below.symbol— plain ticker, e.g.SPY,SPX,BTC/USD, or (forequity-option) a padded OCC symbol. Uppercased automatically. Forequity-option, you can instead pass the underlying/root assymbolplusexpiration_date/option_type/strike_price(see below) and skip building the OCC symbol yourself.interval— one of1m,5m,15m,30m,1h,4h,1d,1w.start_time/end_time— ISO-8601 UTC timestamps, e.g.2026-08-04T13:30:00Z.end_timecannot be in the future. The server also enforces a self-imposed maximum range per interval (for example,1mbars are capped at a 7-day range) purely as a safety guard — this is not a documented tastytrade/DxLink limit, just a sane default; adjustMAX_RANGE_DAYS_BY_INTERVALincandles.pyif you need a wider range.limit— optional cap on the number of bars returned (most recent first), up to 5000.expiration_date/option_type/strike_price— optional; provide all three together with a rootsymbol(e.g.symbol=SPY) to backtest an option directly, for example a same-day (0DTE) SPX put:product_type=equity-option symbol=SPX expiration_date=2026-08-09 option_type=put strike_price=5000 interval=5m start_time=2026-08-09T13:30:00Z end_time=2026-08-09T20:00:00Z
Response shape:
{
"success": true,
"symbol": "SPX",
"product_type": "index",
"interval": "1h",
"start_time": "2026-08-01T13:30:00Z",
"end_time": "2026-08-04T20:00:00Z",
"bars": [
{
"timestamp": "2026-08-04T13:30:00Z",
"open": 7740.2,
"high": 7744.8,
"low": 7738.9,
"close": 7743.6,
"volume": 0
}
],
"bar_count": 1,
"source": "tastytrade-dxlink",
"retrieved_at": "2026-08-04T20:55:00Z"
}
Bars may also include vwap, bid_volume, ask_volume, implied_volatility,
and open_interest when dxfeed reports them for that symbol/interval. volume
is frequently 0 for index symbols (e.g. SPX) since indices don't have real
traded volume; it's populated for equities and, where dxfeed reports it,
cryptocurrency pairs.
This data is not necessarily real-time. It reflects whatever market-data entitlements are on the authenticated tastytrade account and dxfeed's own publishing latency/limits; timestamps are UTC exchange/event times as reported by dxfeed, not wall-clock receipt times.
Historical candles architecture
get_historical_candles is backed by a small DxLink adapter
(src/tastytrade_mcp_server/dxlink.py) rather than a REST call, since
tastytrade only serves historical bars over its DxLink WebSocket streaming
protocol. Each tool call:
- Fetches a short-lived quote-streamer token via
GET /api-quote-tokens(TastytradeClient.quote_token()), reusing the same account auth this server already establishes — no separate credentials are required, and the token is never returned to the MCP client or logged. - Opens one WebSocket connection to the returned
dxlink-url, runs the DxLinkSETUP → AUTH → CHANNEL_REQUEST → FEED_SETUP → FEED_SUBSCRIPTIONhandshake, requests one bounded historical range, collects candle events until the upstream snapshot is complete (or a timeout), and closes the connection. This is a one-shot request/response, not a persistent stream — there is no reconnect/keepalive loop, since nothing in this server holds a connection open between tool calls. - Normalizes the result into the stable shape above
(
src/tastytrade_mcp_server/candles.py), never exposing raw protocol bookkeeping fields (event flags, sequence numbers) or the quote token.
equity-option symbol translation: DxLink's symbol syntax for equity/index
options is a different, compact wire format
(.{root}{YYMMDD}{C/P}{strike}, e.g. .SPXW260727P7250) from the OCC-padded
symbols this server's REST tools build (SPXW 260727P07250000).
client.occ_to_dxlink_option_symbol() implements the confirmed translation
(cross-checked against Option.occ_to_streamer_symbol in the
tastyware/tastytrade Python SDK)
and candles.to_dxlink_symbol() calls it for product_type=equity-option.
Known limitation / documented uncertainty: futures and future-options use
a materially different dxfeed compact symbol syntax (exchange/expiry-code
based, e.g. /ESZ25:XCME) that has no authoritative documentation source
available for this change. Rather than guess, future and future-option
are explicitly rejected by get_historical_candles with a clear validation
error. The translation is isolated behind candles.to_dxlink_symbol(), so
extending support later is a small, contained change once the mapping is
confirmed.
Consuming candles from a strategy worker
This server stays deliberately read-only and does not compute indicators, evaluate trading signals, or place orders — see Why order execution stays out of scope below. The intended usage pattern is a separate local process (e.g. a strategy worker) that:
- Calls
get_historical_candles(and/or the liveget_market_data) to pull bars for the symbols/intervals it cares about. - Maintains its own local candle store, computes indicators, and evaluates signals against them.
- Applies its own risk checks and — separately from this MCP server — decides whether and how to act.
Recommended follow-up architecture (not implemented here): a
calculate_indicators-style capability belongs in that separate worker, not
in this MCP server, so the LLM isn't made responsible for repeated intraday
recalculation.
Why order execution stays out of scope
This server is intentionally read-only. Adding order submission, replacement, or cancellation tools is a separate design discussion (see CONTRIBUTING.md) — mixing broker-execution authority into a tool surface an LLM calls directly significantly raises the blast radius of a mistake or prompt-injected action. Historical/live market data plus account state is enough for local signal generation; trade execution should go through a narrower, more deliberately reviewed path.
Backtesting
run_backtest, simulate_option_trade, list_backtest_symbols,
get_backtest_result, list_backtests, cancel_backtest, and
get_backtest_logs wrap tastytrade's own Backtester Backend API
(documented at
developer.tastytrade.com/open-api-spec/backtesting),
which simulates option/equity strategies against tastytrade's historical data
entirely on tastytrade's own servers. These tools are the intended answer to
"can an agent backtest an idea I describe in plain English" — an agent can
map a request like "backtest selling 0DTE SPX puts every day for the last 6
months" or "what if I sold 45 DTE SPY puts and closed at 21 DTE" directly
onto run_backtest's parameters without you having to hand-build any
payloads. See backtest.py for the
full implementation and field-level docstrings (also surfaced to MCP clients
via each tool's parameter descriptions).
This is a separate service from the rest of this server, on its own host
(backtester.vast.tastyworks.com by default; override with
TASTYTRADE_BACKTEST_API_BASE_URL), and:
- Requires a production account. tastytrade does not offer backtesting on
the sandbox/cert environment. Set
TASTYTRADE_ENV=production; these tools raise a clear error immediately (before any network call) if the server is configured for sandbox. - Bootstraps its own bearer token per call. Each backtesting tool call
exchanges this server's existing tastytrade auth (whatever
TastytradeClient.auth_token()currently resolves to) for a backtester-scoped token viaPOST /sessionson the backtester host, uses it for that one request/poll sequence, then deletes the backtester session. tastytrade doesn't publish this bootstrap step in the service's own OpenAPI spec, and the community Python SDK this was cross-checked against (tastyware/tastytrade) dropped its backtesting module specifically because that exchange historically needed the raw session-token from username/password login rather than an OAuth bearer token. Confirmed live: tastytrade's backtester/sessionsbootstrap accepts (echoes back, without validating) any token you send it, but its actual data endpoints then reject an OAuth access token with401 invalid tastytrade token.TastytradeClient.legacy_session_token()therefore prefersTASTYTRADE_SESSION_TOKEN/TASTYTRADE_USERNAME+TASTYTRADE_PASSWORDover OAuth specifically for backtesting, falling back to OAuth (known not to work) only if neither is configured — everything else about the server can stay on OAuth.- If the account has 2FA "device authentication" protection, a
username/password login from a host tastytrade hasn't seen before returns
403 device_challenge_requiredwith anX-Tastyworks-Challenge-Tokenresponse header. Completing it requires POSTing to/device-challengewith that header plus a liveX-Tastyworks-OTPcode, then resubmitting/sessionswith a second challenge-token (from the/device-challengeresponse) and the same OTP -- a three-step, human-in-the-loop flow this server does not implement. This is a one-time cost: confirmed live that once cleared for a given host, subsequent username/password logins from that same host succeed directly with no further challenge (this appears to be recognition on tastytrade's end -- IP and/or other request fingerprinting -- since this server persists no cookie or device-id of its own between calls). To clear it, complete the three-step flow above once by hand (e.g. a small script posting those requests with a live OTP from your authenticator app);TASTYTRADE_USERNAME/TASTYTRADE_PASSWORDalone is then sufficient going forward -- noTASTYTRADE_SESSION_TOKENneeded, and nothing to refresh.
- If the account has 2FA "device authentication" protection, a
username/password login from a host tastytrade hasn't seen before returns
run_backtest
Creates a backtest and polls it (roughly every 1.5s) until it completes or
max_wait_seconds elapses (default 45s, capped at 180s — tune per call since
real backtests over wide date ranges or frequent-entry strategies like daily
0DTE commonly take well over a minute). If it returns before completion,
status won't be "completed" and the response includes timed_out: true
and the backtest id — pass that to get_backtest_result to keep polling.
A short SPX put sold 0DTE every day, stopped out at 200% of credit received.
tastytrade's backtester rejects days_until_expiration: 0 (400 days until expiration must be between 1 and 730) -- 1 is the closest available
equivalent, entered the day before expiration rather than same-day
open-and-expire (confirmed live: a 1-year run of this exact strategy averaged
1.02 days in trade per position):
{
"symbol": "SPX",
"start_date": "2026-02-01",
"legs": [
{
"type": "equity-option",
"direction": "short",
"side": "put",
"strike_selection": "delta",
"delta": 15,
"days_until_expiration": 1
}
],
"exit_conditions": { "stop_loss_percentage": 200 }
}
Run live against production for 2025-08-07 → 2026-08-07: 198 trades, 89.9% win rate, +$33,733.85 total P&L, 21.96% CAGR, -7.01% max drawdown, 3.13 MAR ratio.
A 45 DTE short SPY put, closed at 21 DTE or 50% profit, entered daily:
{
"symbol": "SPY",
"start_date": "2025-08-01",
"legs": [
{
"type": "equity-option",
"direction": "short",
"side": "put",
"strike_selection": "delta",
"delta": 30,
"days_until_expiration": 45
}
],
"exit_conditions": {
"at_days_to_expiration": 21,
"take_profit_percentage": 50
}
}
Response is the raw tastytrade BacktestGet payload (id, status,
progress, ETA, statistics, trials, snapshots, notices) plus
success/timed_out. statistics includes win/loss counts, total P&L, max
drawdown, return on used capital, premium capture rate, and more; trials
lists each individual trade's open/close time and P&L; snapshots is a
P&L-over-time series.
legs, entry_conditions, and exit_conditions mirror the API's Leg,
EntryConditions, and ExitConditions schemas field-for-field (snake_case
here, translated to the API's camelCase) — see BacktestLegInput /
BacktestEntryInput / BacktestExitInput in backtest.py for every field
and its constraints (delta 1-100, quantity 1-100, etc).
simulate_option_trade
For a one-off "what would this specific trade have cost/returned" question
rather than a repeated-entry strategy backtest — e.g. a covered call, put
ratio spread, or butterfly on a specific date. Legs can be given as a
pre-built symbol or as structured root_symbol/expiration_date/
option_type/strike_price fields, same as the historical-candles options
convenience above.
list_backtest_symbols
Returns tastytrade's supported backtest symbols and each one's valid
historical date range (GET /available-dates) — call this first to confirm a
symbol like SPX or SPY is backtestable and to know how far back its data
goes.
Development
uv run pytest
uv run ruff check .
All get_historical_candles/DxLink tests run against fakes and require no
credentials. To additionally run the opt-in live smoke test against real
tastytrade sandbox/production credentials already in your .env:
TASTYTRADE_LIVE_DXLINK_SMOKE_TEST=1 uv run pytest tests/test_server.py -k live
This is skipped by default and never runs in a normal uv run pytest pass.
Notes
This server returns raw tastytrade API response objects (except
get_historical_candles, which returns a normalized bar shape, and the
backtesting tools, which return tastytrade's raw payload with only a
success/timed_out/note wrapper added — see above for both) so
downstream clients can preserve broker-specific fields. Keep credentials out of
source control; .env is ignored by git.
Install Tastytrade in Claude Desktop, Claude Code & Cursor
unyly install tastytradeInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add tastytrade -- uvx --from git+https://github.com/roymeshulam/tastytrade-mcp-server tastytrade-mcp-serverStep-by-step: how to install Tastytrade
FAQ
Is Tastytrade MCP free?
Yes, Tastytrade MCP is free — one-click install via Unyly at no cost.
Does Tastytrade need an API key?
No, Tastytrade runs without API keys or environment variables.
Is Tastytrade hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Tastytrade in Claude Desktop, Claude Code or Cursor?
Open Tastytrade on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Tastytrade with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
