Schwab MCP
# Schwab MCP
一个**只读**的 Charles Schwab(嘉信理财)[Model Context Protocol](https://modelcontextprotocol.io) 服务器。
Claude、GPT 等 AI agent 可以通过它查询你的**持仓、账户余额、实时报价**、K 线、期权链、订单和交易记录。
> 本项目**不能下单、改单、撤单**,代码里也没有任何交易接口,只负责把 Schwab 的数据安全地交给 agent。
> 非 Schwab 官方项目,也不构成投资建议。
## 特性
- **持仓 + 实时价格**:`get_positions(include_quotes=true)` 一次调用返回每个持仓的成本、市值、浮盈亏、当日盈亏、
仓位占比,并附带实时报价
- **组合总览**:`get_portfolio_summary` 汇总所有账户的总资产、现金、当日盈亏、资产类别配置和前 N 大持仓
(同一只股票在多个账户里的持仓会合并)
- **行情**:实时报价(股票、ETF、指数、期货、期权及希腊值)、K 线、期权链、开闭市时间、涨跌榜、基本面搜索
- **隐私**:账号默认脱敏成 `****1234`,Schwab 的 account hash 不会出现在输出里;agent 可以用尾号或昵称指定账户
- **为 LLM 优化的输出**:snake_case 字段、去掉空值、数字取整、时间统一为美东时间、预先算好百分比,既省 token 又不容易算错
- **自动续期**:access token(30 分钟)自动刷新;refresh token 7 天后过期,重新执行 `schwab-mcp auth` 即可,
**正在运行的 server 不需要重启**
- **两种传输方式**:stdio(Claude Desktop / Claude Code / Codex CLI / OpenAI Agents SDK)和 Streamable HTTP
(必须配置 Bearer token)
## 工具一览
| 工具 | 说明 |
|---|---|
| `get_portfolio_summary` | 全部账户总览:总资产、现金、当日/浮动盈亏、资产配置、前 N 大持仓、各账户占比 |
| `get_positions` | 各账户持仓明细;`include_quotes=true` 附带实时价格;可按账户、按代码过滤(期权按标的匹配) |
| `list_accounts` | 账户列表:类型(MARGIN/CASH)、昵称、总资产、现金、购买力等 |
| `get_quotes` | 实时报价:最新价、买卖盘、涨跌幅、成交量、日内/52 周区间;期权带希腊值;可选基本面 |
| `get_price_history` | K 线(1 分钟到月线)+ 区间统计(涨跌幅、最高最低、均量),支持预设周期或起止日期 |
| `get_option_chain` | 期权链:bid/ask/mark、成交量、未平仓量、IV、希腊值,按到期日排序 |
| `get_option_expirations` | 某个标的的所有期权到期日 |
| `get_market_hours` | 开闭市时间(盘前/盘中/盘后),并标出**当前**所处时段 |
| `get_movers` | 指数/交易所的涨跌榜、成交量榜 |
| `search_instruments` | 按代码或公司名搜索;`fundamental` 模式返回市盈率、市值、beta 等 |
| `get_orders` | 最近 60 天内的订单(含成交明细和平均成交价),只读 |
| `get_transactions` | 交易流水:买卖、分红、利息、出入金、费用 |
| `get_auth_status` | 登录状态,以及距离 7 天过期还剩多久 |
| `get_current_time` | 当前美东时间和 UTC 时间(方便模型做日期计算) |
另外提供一个 prompt 模板 `portfolio_review`,一键生成组合复盘。
## 快速开始
### 1. 申请 Schwab 开发者 App(只需一次)
1. 用你的 Schwab 账号登录 <https://developer.schwab.com>,进入 **Dashboard → Apps → Create App**。
2. API Product 选 **Accounts and Trading Production**(如果能选,再加上 **Market Data Production**)。
3. Callback URL 填 `https://127.0.0.1:8182`(**末尾不要加 `/`**,后续配置必须与这里一字不差)。
4. 创建后的状态通常是 `Approved - Pending`,要等变成 **`Ready For Use`**(可能需要几天)才能使用。
5. 在 App 详情页拿到 **App Key** 和 **Secret**。
### 2. 安装
需要 Python ≥ 3.10,推荐使用 [uv](https://docs.astral.sh/uv/):
```bash
git clone https://github.com/<you>/Schwab-MCP.git
cd Schwab-MCP
uv sync # 或者:python -m venv .venv && .venv/bin/pip install -e .
```
### 3. 配置凭据
```bash
mkdir -p ~/.config/schwab-mcp
cp .env.example ~/.config/schwab-mcp/.env
chmod 600 ~/.config/schwab-mcp/.env
# 编辑该文件,填入 SCHWAB_CLIENT_ID(App Key)和 SCHWAB_CLIENT_SECRET(Secret)
```
配置的读取顺序(先读到的优先):真实环境变量 → `--env-file` / `$SCHWAB_MCP_ENV_FILE` → 当前目录的 `.env`
→ `~/.config/schwab-mcp/.env`。
### 4. 登录(每 7 天一次)
```bash
uv run schwab-mcp auth
```
浏览器会打开 Schwab 登录页 → 登录并勾选要授权的账户 → 浏览器跳转到 `https://127.0.0.1:8182/?code=...`,
**页面打不开是正常的** → 把地址栏里的完整 URL 粘贴回终端。Token 保存在 `~/.config/schwab-mcp/token.json`(权限 600)。
```bash
uv run schwab-mcp status # 查看登录状态,并测试 API 连通性
```
### 5. 接入你的 agent
把下面的 `/ABS/PATH/Schwab-MCP` 换成仓库的绝对路径。如果凭据已经写在 `~/.config/schwab-mcp/.env` 里,
`env` 部分可以省略。
<details open>
<summary><b>Claude Desktop</b></summary>
编辑 `claude_desktop_config.json`(macOS 位于 `~/Library/Application Support/Claude/`):
```json
{
"mcpServers": {
"schwab": {
"command": "uv",
"args": ["--directory", "/ABS/PATH/Schwab-MCP", "run", "schwab-mcp"],
"env": {
"SCHWAB_CLIENT_ID": "your-app-key",
"SCHWAB_CLIENT_SECRET": "your-app-secret"
}
}
}
}
```
Claude Desktop 可能找不到 `uv`。如果启动失败,把 `"command"` 改成 `which uv` 输出的绝对路径。
</details>
<details>
<summary><b>Claude Code</b></summary>
```bash
claude mcp add schwab --scope user -- uv --directory /ABS/PATH/Schwab-MCP run schwab-mcp
```
</details>
<details>
<summary><b>OpenAI Codex CLI(GPT)</b></summary>
`~/.codex/config.toml`:
```toml
[mcp_servers.schwab]
command = "uv"
args = ["--directory", "/ABS/PATH/Schwab-MCP", "run", "schwab-mcp"]
```
</details>
<details>
<summary><b>OpenAI Agents SDK(GPT,本地 stdio)</b></summary>
```python
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async def main():
async with MCPServerStdio(
params={"command": "uv", "args": ["--directory", "/ABS/PATH/Schwab-MCP", "run", "schwab-mcp"]},
) as schwab:
agent = Agent(name="Portfolio assistant", mcp_servers=[schwab],
instructions="用中文回答,只使用工具返回的数据。")
result = await Runner.run(agent, "我今天的持仓表现怎么样?哪只股票拖累最大?")
print(result.final_output)
asyncio.run(main())
```
</details>
<details>
<summary><b>远程 HTTP(OpenAI Responses API 等)</b></summary>
```bash
export SCHWAB_MCP_HTTP_TOKEN=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
uv run schwab-mcp serve --transport http --port 8765 # 端点:http://127.0.0.1:8765/mcp
# 通过隧道或反向代理暴露时,加上 --allowed-host your.domain.com
```
所有请求都必须带 `Authorization: Bearer $SCHWAB_MCP_HTTP_TOKEN`,否则返回 401。
```python
from openai import OpenAI
resp = OpenAI().responses.create(
model="gpt-5", # 任意支持远程 MCP 工具的模型
tools=[{
"type": "mcp",
"server_label": "schwab",
"server_url": "https://your.domain.com/mcp",
"headers": {"Authorization": "Bearer <SCHWAB_MCP_HTTP_TOKEN>"},
"require_approval": "never",
}],
input="列出我的前五大持仓和它们今天的涨跌幅",
)
print(resp.output_text)
```
⚠️ 这相当于把你的券商数据暴露到公网。只在确实需要时使用:token 要足够长,放在 HTTPS 后面,用完就关。
ChatGPT 网页版的自定义连接器只支持 OAuth 或无认证,所以**不建议**直接接入。GPT 用户请优先使用 Codex CLI 或
Agents SDK(本地 stdio,数据只会发给模型,不会暴露在公网上)。
</details>
### 6. 试一试
- 「我的组合今天表现如何?」→ `get_portfolio_summary`
- 「列出我所有持仓的实时价格和浮盈」→ `get_positions(include_quotes=true)`
- 「NVDA 和 AAPL 现在多少钱?」→ `get_quotes`
- 「AAPL 过去 6 个月的走势」→ `get_price_history(period="6M")`
- 「我的 Roth IRA 这个月收到多少分红?」→ `get_transactions(account="Roth IRA", types=["DIVIDEND_OR_INTEREST"])`
- 「现在美股开盘了吗?」→ `get_market_hours`
## 配置项
| 环境变量 | 默认值 | 说明 |
|---|---|---|
| `SCHWAB_CLIENT_ID` | — | App Key(必填) |
| `SCHWAB_CLIENT_SECRET` | — | App Secret(必填) |
| `SCHWAB_CALLBACK_URL` | `https://127.0.0.1:8182` | 必须与开发者后台填写的完全一致 |
| `SCHWAB_TOKEN_PATH` | `~/.config/schwab-mcp/token.json` | Token 文件位置 |
| `SCHWAB_MCP_MASK_ACCOUNTS` | `true` | 账号脱敏;设为 `false` 显示完整账号 |
| `SCHWAB_MCP_HTTP_TOKEN` | — | HTTP 传输使用的 Bearer token(HTTP 模式必填) |
| `SCHWAB_MCP_TIMEOUT` | `20` | 单次请求超时(秒) |
| `SCHWAB_MCP_LOG_LEVEL` | `INFO` | 日志级别(日志只写 stderr,不会干扰 stdio 协议) |
命令行:`schwab-mcp [serve] [--transport stdio|http] [--host] [--port] [--allowed-host HOST]`、
`schwab-mcp auth [--no-browser]`、`schwab-mcp status`,以及全局参数 `--env-file PATH`。
## 安全与隐私
- **只读**:客户端只实现了 GET 接口,所有工具都带 MCP 的 `readOnlyHint` 注解。
- **Token 存储**:`token.json` 以 `0600` 权限原子写入,所在目录为 `0700`。不要把它和 `.env` 提交到 git(`.gitignore` 已排除)。
- **账号脱敏**:输出中的完整账号会被替换成 `****1234`,交易描述之类的自由文本也不例外;hash 值从不输出。
- **数据流向**:agent 调用工具后,返回的数据会发给你所用的模型提供方(Anthropic、OpenAI 等),请按自己的隐私要求取舍。
- **HTTP 模式**:未设置 `SCHWAB_MCP_HTTP_TOKEN` 时拒绝启动;`--insecure-no-auth` 只允许绑定回环地址。
绑定 127.0.0.1 时默认开启 DNS rebinding 防护。
## 常见问题
| 现象 | 处理方法 |
|---|---|
| 工具提示 `Schwab login problem` 或 refresh token 过期 | 运行 `schwab-mcp auth` 重新登录(每 7 天一次,server 无需重启) |
| 登录时 Schwab 提示 callback/redirect 不匹配 | `SCHWAB_CALLBACK_URL` 必须与后台填写的完全一致,包括末尾有没有 `/` |
| HTTP 401 `invalid_client` | App 还没到 `Ready For Use`,或者 Key/Secret 填错了 |
| HTTP 403 | App 没有开通对应的 API Product(账户或行情) |
| HTTP 429 | 触发了 Schwab 的频率限制(约 120 次/分钟),server 会自动退避重试 |
| Claude Desktop 里看不到工具 | 检查 `command` 是否为绝对路径,并查看 Claude Desktop 的 MCP 日志 |
## 开发
```bash
uv sync
uv run pytest # 针对模拟的 Schwab API 运行,不需要真实账户
uv run ruff check src tests
npx @modelcontextprotocol/inspector uv run schwab-mcp # 在浏览器里交互式调试工具
```
代码结构和设计取舍见 [`docs/DESIGN.md`](docs/DESIGN.md)。
## 参考项目
设计参考了以下开源项目,具体借鉴点见 [`docs/DESIGN.md`](docs/DESIGN.md#参考项目):
- [jkoelker/schwab-mcp](https://github.com/jkoelker/schwab-mcp):Python,基于 schwab-py;交易需要审批,`--json` 精简输出
- [sudowealth/schwab-mcp](https://github.com/sudowealth/schwab-mcp):TypeScript + Cloudflare Workers;账号脱敏、日志脱敏
- [alexgolec/schwab-py](https://github.com/alexgolec/schwab-py):社区 Schwab API 客户端,OAuth 流程和接口参数的权威参考
## License
MIT
TDQS
Scored across 14 tools
Most tools target distinct resource/action pairs, and descriptions clarify boundaries. There is some conceptual overlap between get_positions and get_portfolio_summary, and between get_orders and get_transactions, but they are distinguished by detail level and scope.
All tool names use snake_case with a consistent verb_noun pattern such as get_*, list_*, and search_*. The naming is predictable throughout the set.
14 tools is well-scoped for a read-only Schwab brokerage and market-data server. Each tool covers a clear area such as accounts, quotes, options, orders, or time, without excessive duplication.
The read-only surface is strong: auth, accounts, positions, portfolio, orders, transactions, quotes, price history, options, market hours, movers, instruments, and time are all covered. Missing write/trading operations like placing or cancelling orders and minor extras like watchlists or news are the main gaps.