Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Tastytrade

БесплатноНе проверен

Read-only MCP server for tastytrade account data, market metrics, historical candles, backtesting, and trade simulation.

GitHubEmbed

Описание

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+
  • uv or 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
  1. Point DNS at the server:
Host: mcp
Type: A
Value: <server-public-ip>
  1. 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.

  1. 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
  1. 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
  1. 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
  1. 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_accounts
  • get_account_balance
  • get_account_positions
  • get_live_orders
  • search_orders
  • get_account_transactions
  • get_market_data
  • get_market_metrics
  • get_historical_candles
  • list_backtest_symbols
  • run_backtest
  • get_backtest_result
  • list_backtests
  • cancel_backtest
  • get_backtest_logs
  • simulate_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 of equity, equity-option, future, future-option, cryptocurrency, index (same enum as get_market_data), but only equity, equity-option, index, and cryptocurrency are currently supported for candles. future and future-option are 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 (for equity-option) a padded OCC symbol. Uppercased automatically. For equity-option, you can instead pass the underlying/root as symbol plus expiration_date/option_type/strike_price (see below) and skip building the OCC symbol yourself.

  • interval — one of 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w.

  • start_time / end_time — ISO-8601 UTC timestamps, e.g. 2026-08-04T13:30:00Z. end_time cannot be in the future. The server also enforces a self-imposed maximum range per interval (for example, 1m bars are capped at a 7-day range) purely as a safety guard — this is not a documented tastytrade/DxLink limit, just a sane default; adjust MAX_RANGE_DAYS_BY_INTERVAL in candles.py if 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 root symbol (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:

  1. 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.
  2. Opens one WebSocket connection to the returned dxlink-url, runs the DxLink SETUP → AUTH → CHANNEL_REQUEST → FEED_SETUP → FEED_SUBSCRIPTION handshake, 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.
  3. 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:

  1. Calls get_historical_candles (and/or the live get_market_data) to pull bars for the symbols/intervals it cares about.
  2. Maintains its own local candle store, computes indicators, and evaluates signals against them.
  3. 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 via POST /sessions on 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 /sessions bootstrap accepts (echoes back, without validating) any token you send it, but its actual data endpoints then reject an OAuth access token with 401 invalid tastytrade token. TastytradeClient.legacy_session_token() therefore prefers TASTYTRADE_SESSION_TOKEN/TASTYTRADE_USERNAME+ TASTYTRADE_PASSWORD over 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_required with an X-Tastyworks-Challenge-Token response header. Completing it requires POSTing to /device-challenge with that header plus a live X-Tastyworks-OTP code, then resubmitting /sessions with a second challenge-token (from the /device-challenge response) 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_PASSWORD alone is then sufficient going forward -- no TASTYTRADE_SESSION_TOKEN needed, and nothing to refresh.

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.

from github.com/roymeshulam/tastytrade-mcp-server

Установить Tastytrade в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
unyly install tastytrade

Ставит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.

Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh

Или настроить вручную

Выполни в терминале:

claude mcp add tastytrade -- uvx --from git+https://github.com/roymeshulam/tastytrade-mcp-server tastytrade-mcp-server

Пошаговые гайды: как установить Tastytrade

FAQ

Tastytrade MCP бесплатный?

Да, Tastytrade MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Tastytrade?

Нет, Tastytrade работает без API-ключей и переменных окружения.

Tastytrade — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Tastytrade в Claude Desktop, Claude Code или Cursor?

Открой Tastytrade на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Tastytrade with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development