内容
# schwab-positions-mcp
> **🔒 只读设计 - 详见 [docs/SECURITY.md](docs/SECURITY.md)**
>
> 没有 `place_order`、`cancel_order`、`replace_order` 接口。此 MCP 服务器向 LLM 代理公开 Charles Schwab 账户状态,**从不**暴露修改端点。通过 5 层边界强制执行(运行时允许列表 + 启动警告 + 工具表面审计 + CI grep 门控 + 拒绝测试)。
[](https://github.com/kevinkda/schwab-positions-mcp/actions/workflows/test.yml)
[](https://github.com/kevinkda/schwab-positions-mcp/actions/workflows/codeql.yml)
[](https://github.com/kevinkda/schwab-positions-mcp/actions/workflows/security-grep.yml)
[简体中文](README_zh.md)
`schwab-positions-mcp` 是一个 MCP(模型上下文协议)服务器,提供 **只读** 的 Charles Schwab 券商账户视图 - 持仓、订单、交易和账户余额 - 给任何 MCP 兼容的 LLM 客户端(Claude Desktop、Cursor 等)。它是 [`schwab-marketdata-mcp`](https://github.com/kevinkda/schwab-marketdata-mcp) 的配套工具,后者公开 Schwab 市场数据生产端点。两个仓库故意分开,以便交易账户凭证集生活在其自己的进程和配置目录中。
## 工具(14)
### 账户 / 投资组合(9)
| 工具 | 描述 |
| --- | --- |
| `get_accounts` | 列出所有关联的 Schwab 账户。可选 `fields=["positions"]` 在行内扩展持仓。 |
| `get_account_numbers` | 将明文 `accountNumber` 映射到加密的 `account_hash`(其他所有工具都需要)。 |
| `get_account_positions` | 获取一个账户的持仓 + 余额;如果启用缓存,则将持仓快照保存到派生历史缓存中。 |
| `get_orders_history` | 返回两个时区感知 datetime 之间的订单(Schwab 限制回溯 60 天)。 |
| `get_transactions` | 返回两个 ISO 日期之间的交易(TRADE / DIVIDEND_OR_INTEREST 等)。 |
| `get_user_preferences` | 返回账户用户偏好设置(默认账户、昵称、流媒体路由)。无参数,无缓存写入。 |
| `get_order_detail` | 通过数字 `order_id` 读取一个订单的完整详细信息(状态 / 腿 / 填充)。只读 - 永远不会下单 / 取消 / 替换。 |
| `get_transaction_detail` | 通过 `transaction_id` 读取一个历史交易的详细信息。只读 - 无资金变动。 |
| `get_account_summary` | 紧凑汇总:持仓数量、总市值、总盈亏、现金、购买力、余额。 |
### 派生分析(3,只读 - 纯计算,无缓存写入)
| 工具 | 描述 |
| --- | --- |
| `get_pnl_analysis` | 每持仓成本基数 / 未实现盈亏 / 未实现 %、交易衍生实现盈亏和投资组合汇总。**成本基数方法:平均成本**(持仓饲料仅公开 `averagePrice`;无 FIFO 的每批次记录)。 |
| `get_concentration_analysis` | 顶部 N 个权重、赫芬达尔-赫希曼指数(HHI)、最大单一持仓权重和资产类型暴露。板块暴露为 `N/A` - Schwab 持仓饲料无 GICS 板块字段。 |
| `get_cross_account_summary` | 展开 `get_account_numbers` → `get_account` 每个账户,然后将持仓 + 余额合并成一个综合视图,带有每个账户的份额和符号级去重。 |
### 元数据(2)
| 工具 | 描述 |
| --- | --- |
| `health_check` | 就绪探测 - 报告凭证 / token 存在情况,无需联系 Schwab。 |
| `get_server_info` | 服务器元数据 - 版本、平台、 **`is_read_only: true`**、工具列表。 |
## 安装
```bash
git clone https://github.com/kevinkda/schwab-positions-mcp.git
cd schwab-positions-mcp
uv sync --extra dev
```
需要 Python ≥ 3.11 和已安装的 Schwab Trader API 客户端应用程序(参见 [docs/REGISTER.md](docs/REGISTER.md))。
## 配置
```bash
cp .env.example .env
# 编辑 .env 以设置 SCHWAB_API_KEY、SCHWAB_APP_SECRET、SCHWAB_CALLBACK_URL
```
## OAuth(一次性)
```bash
uv run python -m schwab_positions_mcp.auth login_flow
# 或,如果浏览器自动重定向失败:
uv run python -m schwab_positions_mcp.auth manual_flow
```
令牌保存在 `~/.config/schwab-positions-mcp/token.json`(模式 `0o600`,与 `schwab-marketdata-mcp` 分隔的目录)。
> **OAuth 范围。** Schwab 需要每个令牌的 `trade` 范围,包括只读持仓 / 订单 / 交易调用。因此,令牌具有 **交易能力**。**此 MCP 服务器在代码层阻止每个修改调用**,以便受损的 LLM 无法使用它下单。有关 5 层合约,请参见 [docs/SECURITY.md](docs/SECURITY.md)。
## 运行
```bash
uv run schwab-positions-mcp # MCP 标准输入输出传输
# 或
uv run python -m schwab_positions_mcp # 等效
```
通过将二进制文件指向通常的 MCP `command` + `args` 形状,连接到 Claude Desktop / Cursor。
## 安全
- **只读合约:** 参见 [docs/SECURITY.md](docs/SECURITY.md)。
- **威胁模型:** 参见 [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md)。
- **令牌存储:** `~/.config/schwab-positions-mcp/token.json`,`0o600`,从不提交。
- **CI:** `test.yml`(lint / type / unit),`codeql.yml`(CodeQL),`security-grep.yml`(no-trade 门控)。
## 缓存
可插拔的缓存可以保存只读 **派生历史快照**(持仓 / 订单 / 交易),以便您的 LLM 代理可以运行“自上周以来发生了什么变化”查询,而无需不断联系 Schwab。
缓存**默认禁用(需选择启用)** - 每个工具调用都会实时联系 Schwab。通过 `SCHWAB_POSITIONS_CACHE_ENABLED=true` 启用它(也接受 `1` / `yes` / `on`)。当禁用时,工具返回 `_cache_status: "skipped:disabled"`;响应形状不变。
### 缓存后端(v0.3.0)
⚠️ **BREAKING(v0.3.0):** 嵌入式 DuckDB 缓存因可插拔后端而被删除,通过 `SCHWAB_POSITIONS_CACHE_BACKEND` 选择:
| 后端 | 默认 | 依赖 | 备注 |
| --- | --- | --- | --- |
| `memory` | ✅ | 无(标准库) | 进程内、并发安全、非阻塞、无文件。**无持久历史** - 快照写入报告 `snapshot_written:0`(优雅降级)。 |
| `clickhouse` | - | `pip install schwab-positions-mcp[clickhouse]` + `SCHWAB_POSITIONS_CLICKHOUSE_URL` | 可持久保存持仓 / 订单 / 交易历史。 |
缓存层仅**写入**派生历史 - 它从不将读取反馈到工具中,因此它无法扩大 5 层只读边界。没有 ClickHouse,历史持久性会降级为 `requires_clickhouse_persistence` 信号,并且只读工具完全不受影响。
## 许可证
MIT - 参见 [LICENSE](LICENSE)。