Schwab MCP
Schwab Model Context Protocol (MCP) サーバーは、SchwabアカウントをLLMベースのアプリケーション(Claude Desktopやその他のMCPクライアントなど)に接続し、マーケットデータの取得、口座情報の確認、および(オプションで)あなたの監督下での注文発行を可能にします。
ドキュメント
Schwab Model Context Protocol Server
The Schwab Model Context Protocol (MCP) Server connects your Schwab account to LLM-based applications (like Claude Desktop or other MCP clients), allowing them to retrieve market data, check account status, and (optionally) place orders under your supervision.
Features
- Market Data: Real-time quotes, price history, option chains, and market movers.
- Account Management: View balances, positions, and transactions.
- Trading: comprehensive support for equities and options, including complex strategies (OCO, Bracket).
- Safety First: Critical actions (like trading) are gated behind a Discord approval workflow by default.
- LLM Integration: Designed specifically for Agentic AI workflows.
Quick Start
Prerequisites
- Python 3.10 or higher
- uv (recommended) or
pip - A Schwab Developer App Key and Secret (from the Schwab Developer Portal)
Installation
For most users, installing via uv tool or pip is easiest:
# Using uv (recommended for isolation)
uv tool install 'schwab-mcp[ta] @ git+https://github.com/jkoelker/schwab-mcp.git'
# Using pip
pip install 'schwab-mcp[ta] @ git+https://github.com/jkoelker/schwab-mcp.git'
The ta extra installs pandas and pandas-ta-classic for the technical-analysis
tools (SMA, RSI, ...). Without it those tools are skipped at startup with a
warning; pass --no-technical-tools to run without them deliberately. Add the
signal extra (schwab-mcp[ta,signal]) for Signal approvals.
Pass secrets through environment variables (or schwab-mcp save-credentials)
rather than command-line flags, which are visible to other local users in
ps and end up in shell history:
export SCHWAB_CLIENT_ID=YOUR_KEY
export SCHWAB_CLIENT_SECRET=YOUR_SECRET
export SCHWAB_MCP_DISCORD_TOKEN=BOT_TOKEN # only for Discord approvals
Authentication
Before running the server, you must authenticate with Schwab to generate a token file.
# If installed via uv tool or pip (with SCHWAB_CLIENT_ID / SCHWAB_CLIENT_SECRET exported)
schwab-mcp auth --callback-url https://127.0.0.1:8182
# If running from source
uv run schwab-mcp auth --callback-url https://127.0.0.1:8182
This will open a browser window for you to log in to Schwab. Once complete, a token will be saved to ~/.local/share/schwab-mcp/token.yaml.
Running the Server
Start the MCP server to expose the tools to your MCP client.
# Credentials come from SCHWAB_CLIENT_ID / SCHWAB_CLIENT_SECRET (see above).
# Basic Read-Only Mode (Safest)
schwab-mcp server
# Streamable HTTP (for reverse proxies / remote MCP connectors)
schwab-mcp server --http --host 127.0.0.1 --port 8000
# With Trading Enabled (Requires Discord Approval; token from SCHWAB_MCP_DISCORD_TOKEN)
schwab-mcp server \
--discord-channel-id CHANNEL_ID \
--discord-approver YOUR_USER_ID
# Or approve over Signal instead (requires a local bbernhard/signal-cli-rest-api
# daemon running in MODE=json-rpc or json-rpc-native; other modes cannot
# stream replies and silently consume them)
schwab-mcp server \
--signal-account +15555550100 \
--signal-approver +15555550199
Default transport is stdio (Claude Desktop and most local MCP clients).
Use --http for FastMCP streamable-http when fronting the server with a
gateway or remote connector (MCP endpoint is /mcp on the bound host:port).
Note: For trading capabilities, you must set up an approval backend: either a Discord bot (see Discord Setup Guide) or Signal via a local signal-cli REST daemon running in
MODE=json-rpc(orjson-rpc-native). Configure either backend, not both. (Thesignalextra declares the websockets dependency explicitly; a default install already includes it transitively.)
Configuration
You can configure the server using CLI flags or Environment Variables.
| Flag | Env Variable | Description |
|---|---|---|
--client-id | SCHWAB_CLIENT_ID | Required. Schwab App Key. |
--client-secret | SCHWAB_CLIENT_SECRET | Required. Schwab App Secret. |
--callback-url | SCHWAB_CALLBACK_URL | Redirect URL (default: https://127.0.0.1:8182). |
--token-path | N/A | Path to save/load token (default: ~/.local/share/...). |
--http | N/A | Use streamable-http transport instead of stdio. |
--host | MCP_HOST | Bind address when using --http (default: 127.0.0.1). |
--port | MCP_PORT | Bind port when using --http (default: 8000). |
--jesus-take-the-wheel | N/A | DANGER. Bypasses Discord approval for trades. |
--no-technical-tools | N/A | Disables technical analysis tools (SMA, RSI, etc.). |
--json | N/A | Returns JSON instead of formatted text (useful for some agents). Null/empty fields are stripped to reduce token usage. |
Container Usage
A Docker/Podman image is available at ghcr.io/jkoelker/schwab-mcp.
podman run --rm -it \
--env SCHWAB_CLIENT_ID=... \
--env SCHWAB_CLIENT_SECRET=... \
-v ~/.local/share/schwab-mcp:/schwab-mcp \
ghcr.io/jkoelker/schwab-mcp:latest server --token-path /schwab-mcp/token.yaml
Available Tools
The server provides a rich set of tools for LLMs.
📊 Market Data
| Tool | Description |
|---|---|
get_quotes | Real-time quotes for symbols. |
get_market_hours | Market open/close times. |
get_movers | Top gainers/losers for an index. |
get_option_chain | Standard option chain data. |
get_price_history_* | Historical candles (minute, day, week). |
💼 Account Info
| Tool | Description |
|---|---|
get_accounts | List linked accounts (pass include_positions=True for holdings). |
get_account | Balances for one account by hash (pass include_positions=True for holdings). |
get_transactions | History of trades and transfers. |
get_orders | Status of open and filled orders. |
💸 Trading (Requires Approval)
Placing an order is two steps: preview it, then place it by ID. Every
preview_* tool builds the order and calls Schwab's preview API to return
the projected order details plus a preview_id; place_previewed_order
then submits that exact previewed order — no re-derivation from parameters,
so the LLM can't hallucinate a different order than what was reviewed.
Replacing an order uses the same safe flow: preview a complete single-leg
equity, option, or equity trailing-stop description with
preview_replacement_order, then call replace_previewed_order for the
bound target order.
| Tool | Description |
|---|---|
preview_equity_order | Preview a stock/ETF buy or sell. |
preview_option_order | Preview an option contract buy or sell. |
preview_bracket_order | Preview an entry + take-profit + stop-loss order. Stop-loss exit type defaults to STOP; pass loss_type (STOP, STOP_LIMIT, or LIMIT) for a different exit, plus loss_limit_price when loss_type is STOP_LIMIT; the response's resolved_leg_types shows what was actually built. |
preview_replacement_order | Preview a complete single-leg replacement for an existing order. |
place_previewed_order | Place the exact order returned by a preview_* call, by preview_id. Requires approval. |
replace_previewed_order | Replace the exact order bound to a replacement preview. Requires approval. |
cancel_order | Cancel an open order. |
(See full tool list in src/schwab_mcp/tools/)
Development
To contribute to this project:
# Clone and install dependencies
git clone https://github.com/jkoelker/schwab-mcp.git
cd schwab-mcp
uv sync
# Run tests
uv run pytest
# Format and Lint
uv run ruff format . && uv run ruff check .
Mutation testing
Run a local mutation campaign with make mutation (two workers). The weekly
GitHub Actions workflow runs Sundays at 07:29 UTC and also supports manual
runs. CI limits the mutation step to 20 minutes and saves reports for 14 days;
surviving mutants are informational and do not fail the build by score.
License
MIT License.