MCP server for E*TRADE — OAuth 1.0a + VIP-TOTP auto-renew, account reads always-on, order placement strictly opt-in.
Talk to your E*TRADE account from Claude Code (or any MCP client): list accounts, check balances and positions, pull transactions and open orders — and, only if you turn it on, preview and place equity/option orders (including multi-leg spreads) through a code-enforced two-step handshake.
- You have (or can get) an E*TRADE brokerage account and an MCP-compatible AI client.
- You want your assistant to read balances, positions, transactions, and orders directly, instead of pasting screenshots into a chat.
- You may also want it to place orders — but only behind an explicit, separate opt-in, never by default.
- You're fine getting your own E*TRADE developer API key. This project ships no credentials, no shared account, no proxy service — every install talks straight to E*TRADE with keys you own.
Probably not for you if you want a broker-agnostic tool (this wraps the E*TRADE Account + Order APIs specifically) or a "fully autonomous, no human review" trading bot — the write tools exist precisely so nothing reaches the market without a preview you can read first.
This is a tool that can, if you ask it to, put real orders into a real brokerage account. The
safety properties below are enforced in code (src/mcp.ts, src/orders.ts, src/order-store.ts,
src/tools/), not just written down:
- Read-only unless you opt in.
etrade_preview_order,etrade_preview_spread,etrade_place_order, andetrade_cancel_orderare only registered with the MCP server whenETRADE_ALLOW_ORDERS=1is set. Without it, the server can read accounts, balances, positions, transactions, and orders — and cannot create, modify, or cancel anything. - Two-step preview → place, enforced server-side.
etrade_place_orderaccepts only apreviewId(plusconfirm: true) from a recentetrade_preview_order/etrade_preview_spreadcall. It replays the exact envelope that was previewed — there is no code path that lets a place call carry raw, un-previewed order parameters. - Previews are single-use and short-lived. Each
previewIdis consumed on first use and expires ~3 minutes after issue (mirroring E*TRADE's own preview window); a second attempt or a stale one is rejected and you have to re-preview. - Explicit confirmation.
etrade_place_orderandetrade_cancel_orderboth requireconfirm: true— a Zodliteral(true), re-checked again in the handler. - Sandbox and prod can't cross. A preview minted while the server is running against
ETRADE_ENV=sandboxis refused if you try to place it against aprodserver, and vice versa. - No bulk or irreversible operations. One order per call. There is no "cancel all" or "liquidate everything" tool.
- Ambiguous outcomes are never silently retried. If E*TRADE's place/cancel response comes back
without a clear success or an explicit error (a network timeout, an odd payload), the tool reports
the outcome as UNKNOWN and tells you to check
etrade_list_ordersbefore doing anything else — it never auto-retries a place, which could double an order. - Order shaping is validated before anything reaches E*TRADE — quantity/action/price-field
combinations, E*TRADE's own coupling rules (e.g.
MARKETorders must beGOOD_FOR_DAY, extended-hours orders must beLIMIT, all-or-none needs 300+ shares), and, for spreads, that every leg is a distinct contract on the same underlying. Bad input fails locally with a specific message instead of round-tripping to E*TRADE first. - Not LIMIT-only. To be precise about what the code does and doesn't do:
MARKET,LIMIT,STOP, andSTOP_LIMITare all supported order price types — this package does not force limit-only discipline on you. If you want that discipline, enforce it in whatever is calling these tools.
Your keys, your account, your risk. This is not investment advice, and nothing here is a recommendation about what to trade. You are responsible for every order this places.
etrade-mcp needs its own E*TRADE developer API key and a one-time OAuth login before it can do anything — there is no bundled or shared credential (see Getting an E*TRADE developer key below).
1. Get sandbox credentials at developer.etrade.com (see below).
2. Log in once. E*TRADE's OAuth authorize step is an interactive browser login, but the CLI that drives it runs straight from npm — no clone needed:
ETRADE_ENV=sandbox ETRADE_SANDBOX_API_KEY=<your-key> ETRADE_SANDBOX_API_KEY_SECRET=<your-secret> \
npx -y --package=etrade-mcp etrade-mcp-auth--package=etrade-mcp is required here: a bare npx etrade-mcp runs the package's default bin
(etrade-mcp, the MCP server itself), not this non-default etrade-mcp-auth bin — the
--package=<name> form is npx's way of installing one package and running a different named bin
from it. bunx --package=etrade-mcp etrade-mcp-auth works the same way if you're on Bun.
This opens (prints) an E*TRADE authorize URL, and once you approve it and paste back the 5-digit
verifier, writes an access token to ~/.config/etrade-mcp/tokens.sandbox.json (0600
permissions). See Auth model & token lifecycle for renewal and the
other auth CLIs. (Contributing, or want to run from source instead? git clone the repo — see
Testing.)
3. Register the published server with your MCP client — reads are always on; writes need the explicit opt-in:
claude mcp add etrade \
--env ETRADE_ENV=sandbox \
--env ETRADE_SANDBOX_API_KEY=<your-key> \
--env ETRADE_SANDBOX_API_KEY_SECRET=<your-secret> \
-- npx -y etrade-mcpbunx -y etrade-mcp works the same way if you're on Bun. The credential env vars sign requests;
the actual session token comes from the file step 2 wrote to disk. To also enable order placement,
add --env ETRADE_ALLOW_ORDERS=1 and register it as a separate, deliberately-named server (e.g.
etrade-trade) so the read-only server can stay registered without the write surface attached —
see Tools below.
Once you've validated the flow against sandbox, repeat with a production key (ETRADE_ENV=prod,
ETRADE_PROD_API_KEY / ETRADE_PROD_API_SECRET) — ETRADE_ENV defaults to prod, so a bare
install targets your real account unless you explicitly opt into sandbox.
E*TRADE's API is not a shared or public API — every consumer needs their own key, scoped to their own account. This project ships no credentials of any kind.
- Sandbox key (start here): request one at developer.etrade.com. Sandbox targets E*TRADE's canned test data — it validates the request/response shape, not real fills or real account data.
- Production key: once you're happy against sandbox, request a production key at the same site. Order placement specifically requires signing E*TRADE's API Developer Agreement and completing a User Intent Survey.
- Put the resulting consumer key + secret in your environment — a local
.envin the clone, your shell profile, or your MCP client's--envflags — under the names in the Environment variables table below.
E*TRADE uses OAuth 1.0a (not OAuth2): a 3-legged flow — request a token, a human authorizes it in a
browser, exchange a short verifier code for an access token. This package implements that directly
(oauth-1.0a + HMAC-SHA1 request signing), no vendor SDK:
npx -y --package=etrade-mcp etrade-mcp-auth(or, from a clone,bun run auth) — the interactive one-shot: fetches a request token, prints the E*TRADE authorize URL, waits for you to open it and log in, and reads back the 5-digit verifier E*TRADE shows you.etrade-mcp-auth-start/etrade-mcp-auth-finish <verifier>(both vianpx -y --package=etrade-mcp <bin>, or from a clonebun run auth:start/bun run auth:finish <verifier>) — the same flow split in two for scripting:auth-startprints just the authorize URL and stashes the pending request token;auth-finish(verifier as an argument, orETRADE_VERIFIER) completes it later.
The resulting access token is written to ~/.config/etrade-mcp/tokens.<env>.json with 0600
permissions.
Expiry. E*TRADE access tokens expire at midnight US Eastern (there is no refresh token) and go idle after about 2 hours without an API call.
npx -y --package=etrade-mcp etrade-mcp-renew(or, from a clone,bun run renew) tries E*TRADE's browser-freerenew_access_tokenendpoint first. If the token has only gone idle (not past the midnight-ET hard expiry), this reactivates the same token with zero browser interaction — exit code0means it worked, exit code3means the token is past saving and you need the fulletrade-mcp-authbrowser flow again.- Past midnight ET, only the full 3-legged browser flow can mint a new token — an E*TRADE platform
limit (no refresh tokens), not something this package can route around. Re-run
etrade-mcp-auth(or theetrade-mcp-auth-start/etrade-mcp-auth-finishpair) once a day.
E*TRADE's 2FA is Symantec VIP Access. If you provision a VIP soft-token credential (e.g. via the
vipaccess CLI's provision command) you get a base32 secret. Store it as ETRADE_TOTP_SECRET and
npx -y --package=etrade-mcp etrade-mcp-totp (or, from a clone, bun run totp) prints the current
6-digit code — RFC 6238 TOTP generated locally with node:crypto only (no network call, no
third-party TOTP dependency) — so you don't need your phone during the browser login step.
npx -y --package=etrade-mcp etrade-mcp-login-fill (or, from a clone, bun run login:fill; reads
ETRADE_LOGIN_USERNAME / ETRADE_LOGIN_PASSWORD) drives a
Chromium-family browser already open on E*TRADE's login page over the Chrome DevTools Protocol
(CDP_PORT, default 9333), filling the username and password fields with trusted input events. It
fills only those two fields — never the 2FA/verifier step — and never prints the credential values,
only their lengths. It's a building block for your own re-auth automation, not a turnkey unattended
login.
| Tool | Description |
|---|---|
etrade_list_accounts |
List all E*TRADE accounts for the authenticated user. |
etrade_get_balance |
Cash balance, buying power, and NAV for one account. |
etrade_get_portfolio |
Current positions for one account. |
etrade_list_transactions |
Transactions for one account in a date range. |
etrade_get_transaction |
Detail for a single transaction. |
etrade_list_orders |
Orders for one account. status=OPEN shows working orders; status=EXECUTED confirms a fill before placing a paired stop. |
etrade_snapshot |
Fan-out snapshot across all accounts: balances + positions (+ optional recent transactions). |
| Tool | Description |
|---|---|
etrade_preview_order |
Step 1 (single leg). Validates an equity or single-option order and returns E*TRADE's own cost/commission estimate plus a previewId. Nothing is sent to market. |
etrade_preview_spread |
Step 1 (multi-leg). Validates a 2–4-leg option spread (verticals, calendars, straddles) on one underlying, priced on the net (NET_DEBIT/NET_CREDIT/NET_EVEN), and returns the net cost estimate plus a previewId. |
etrade_place_order |
Step 2. Sends the order. Accepts only a previewId + confirm: true, and replays the exact previewed order — executes both single-leg and spread previews. |
etrade_cancel_order |
Cancels an open order by orderId + confirm: true. An order already routed to or filled at market can't be cancelled. |
Equities, single options, and multi-leg option spreads (2–4 legs, e.g. a defined-risk vertical) are
all supported — a spread is one E*TRADE SPREADS order whose legs fill as a unit at the net price,
distinct from placing separate single-leg orders. The API has no native bracket/OCO/contingent order
type — a "stop attached to an entry" is done client-side: place the entry, confirm the fill with
etrade_list_orders, then place the stop as its own order.
accountIdKey (used across every account-scoped tool) is the obfuscated key etrade_list_accounts
returns — not the plain account number.
| Variable | Required | Purpose |
|---|---|---|
ETRADE_ENV |
optional | sandbox or prod. Defaults to prod. |
ETRADE_SANDBOX_API_KEY / ETRADE_SANDBOX_API_KEY_SECRET |
for ETRADE_ENV=sandbox |
Your E*TRADE sandbox consumer key/secret. |
ETRADE_PROD_API_KEY / ETRADE_PROD_API_SECRET |
for ETRADE_ENV=prod |
Your E*TRADE production consumer key/secret. |
ETRADE_ALLOW_ORDERS |
optional | Set to 1 to register the write tools (preview/place/cancel). Anything else, or unset, is read-only. |
ETRADE_VERIFIER |
optional | Verifier code for etrade-mcp-auth-finish (or pass it as a CLI argument). |
ETRADE_TOTP_SECRET |
optional | Base32 VIP TOTP secret, for etrade-mcp-totp. |
ETRADE_LOGIN_USERNAME / ETRADE_LOGIN_PASSWORD |
optional | Used only by etrade-mcp-login-fill's browser automation. |
CDP_PORT |
optional | Debug port for etrade-mcp-login-fill's browser automation. Defaults to 9333. |
There are a handful of independent E*TRADE MCP servers already on GitHub, mostly small, Python-based
projects with a handful of stars or fewer. None of them are published to npm, so none are
npx-installable — every one requires cloning and a Python toolchain (uvx/pip). The most
established of them, davdunc/mcp_etrade, is a good
reference point:
etrade-mcp (this project) |
davdunc/mcp_etrade |
|
|---|---|---|
| Runtime | TypeScript on Node/Bun | Python 3.11+ |
| Install | npx -y etrade-mcp (published to npm) |
uvx --from git+... (not on npm/PyPI) |
| Order safety | Code-enforced two-step preview → place, single-use ~3-min previewId, sandbox/prod isolation |
Order validation via risk-guardrail tools |
| Multi-leg option spreads | Yes (2–4-leg SPREADS) |
Not documented |
| 2FA helper | Built-in VIP TOTP generator + optional CDP login-fill | Not documented |
| Watch lists / R-multiple risk sizing | No | Yes |
This project's focus is narrower and deeper on one thing — a safe, well-tested order-placement
handshake, installable in one command — rather than a broader feature surface. If you want watch
lists or built-in position-sizing math, mcp_etrade covers ground this one doesn't.
bun test # Unit tests (fast, no network)
bun run test:integration # Manual: requires a valid sandbox token on disk
# Opt-in, PREVIEW-ONLY order smoke (never places an order) — run with the market closed:
ETRADE_RUN_INTEGRATION=1 ETRADE_RUN_ORDER_PREVIEW=1 \
ETRADE_TEST_ACCOUNT_KEY=<accountIdKey> ETRADE_TEST_SYMBOL=AAPL \
bun test src/__tests__/integration.test.ts
# Opt-in, PREVIEW-ONLY multi-leg SPREAD smoke (never places) — validates the SPREADS
# envelope against the live preview endpoint. Defaults to a SOXX Jul-17 $630/$660 call
# vertical; override the contract with the ETRADE_TEST_SPREAD_* vars:
ETRADE_RUN_INTEGRATION=1 ETRADE_RUN_ORDER_SPREAD_PREVIEW=1 \
ETRADE_TEST_ACCOUNT_KEY=<accountIdKey> \
[ETRADE_TEST_SPREAD_SYMBOL=SOXX ETRADE_TEST_SPREAD_EXPIRY=2026-07-17 \
ETRADE_TEST_SPREAD_LONG_STRIKE=630 ETRADE_TEST_SPREAD_SHORT_STRIKE=660] \
bun test src/__tests__/integration.test.tssrc/index.ts also exports the client/auth/order-shaping/token/TOTP layer directly, so you can use
this as a library instead of (or alongside) the MCP server — see the file's header comment for what
is and isn't included.
Four runtime dependencies: @modelcontextprotocol/sdk, oauth-1.0a, zod, dotenv. No database,
no external service beyond E*TRADE's own API.
MIT © 2026 Stephen Blatt
