MCP¶
Model Context Protocol server:把网关的 19 个工具暴露给 LLM 客户端。两种 transport:stdio / HTTP。
19 个工具¶
行情(qot:read,11 个)¶
| 工具 | 说明 |
|---|---|
futu_ping |
ping 网关,返回 RTT |
futu_get_quote |
实时报价(自动订阅 Basic) |
futu_get_snapshot |
快照(含 52 周高低、换手率、买卖档) |
futu_get_kline |
历史 K 线(日 / 周 / 月 / 1-60 分钟) |
futu_get_orderbook |
买卖盘(自动订阅 OrderBook) |
futu_get_ticker |
逐笔成交 |
futu_get_rt |
分时图 |
futu_get_static |
静态信息(名称、每手股数、上市日) |
futu_get_broker |
经纪商队列(港股) |
futu_list_plates |
板块列表 |
futu_plate_stocks |
板块成分股 |
账户只读(acc:read,5 个)¶
| 工具 | 说明 |
|---|---|
futu_list_accounts |
交易账户列表 |
futu_get_funds |
资金概览 |
futu_get_positions |
持仓 |
futu_get_orders |
当日订单 |
futu_get_deals |
当日成交 |
交易(trade:real / trade:simulate,3 个)¶
| 工具 | 说明 | 额外参数 |
|---|---|---|
futu_place_order |
下单 | api_key? per-call 覆盖 |
futu_modify_order |
改单(含撤单) | api_key? |
futu_cancel_order |
撤单(简化版) | api_key? |
解锁交易(trade:unlock,v1.4+,1 个)¶
| 工具 | 说明 | 额外参数 |
|---|---|---|
futu_unlock_trade |
解锁 / 锁回真实交易 | unlock: bool(默认 true) |
- 明文密码永不入 LLM prompt:服务端从 OS keychain 读(优先)或
FUTU_TRADE_PWD环境变量(次选) - 运维用
futucli set-trade-pwd把密码写进 keychain;LLM 只调futu_unlock_trade工具,密码流转完全不经过模型 - MD5 在服务端本地计算后再发给 gateway
- 解锁后 gateway 进程级缓存 cipher,直到 gateway 重启;下单不需要每次重解锁
启动¶
export FUTU_MCP_API_KEY="fc_xxxx..."
./futu-mcp \
--gateway 127.0.0.1:11111 \
--keys-file ~/.config/futu/keys.json
stdin / stdout 传 JSON-RPC 帧,由 LLM 客户端启动子进程。
./futu-mcp \
--gateway 127.0.0.1:11111 \
--keys-file ~/.config/futu/keys.json \
--http-listen 127.0.0.1:38765
POST /mcp—— rmcp streamable HTTP transportGET /metrics—— Prometheus(无需 token)GET /.well-known/oauth-protected-resource—— OAuth2 Protected Resource Metadata(RFC 9728,v1.4+;让 MCP 客户端自动发现 scope 要求)
OAuth metadata 端点(v1.4+)¶
未带 Authorization: Bearer 头的 /mcp 请求会返回 401 + WWW-Authenticate:
Bearer resource_metadata="/.well-known/oauth-protected-resource" 头。
客户端按这个 URL 拉 JSON 即可知道要带什么 scope:
{
"resource": "/mcp",
"bearer_methods_supported": ["header"],
"scopes_supported": [
"qot:read",
"acc:read",
"trade:simulate",
"trade:real",
"trade:unlock"
],
"resource_name": "FutuOpenD-rs MCP",
"resource_documentation": "https://futuapi.com/guide/mcp/"
}
为什么不是完整 OAuth server?
Futu 的 API key 是人工配置的长期凭据,没有 interactive consent 流程。
RFC 9728 正是为"只声明鉴权要求、不做授权流程"的资源服务器设计。
实际 token(= API key)仍由运维线下发放,写进 MCP client 配置里
的 Authorization 头。
per-call API key 优先级(v1.1+)¶
1. tool args 里显式的 `api_key` 字段
2. HTTP Authorization: Bearer <token>(仅 HTTP transport)
3. 启动时 --api-key / FUTU_MCP_API_KEY 环境变量
stdio 模式下没 HTTP header → 自动回落 2→3。HTTP 模式下多 LLM 各带自己 token,audit / 限额各记各的。
scope 中央注册表¶
guard::scope_for_tool(name) -> Option<ToolScope> 是唯一真源。加新 #[tool] 必须同步更新这里,否则 handler 返回 unknown MCP tool;测试 all_known_tools_have_scopes 会挂。
SIGHUP 热重载¶
改了 keys.json 里某把 key 的 scope / 限额 / expires_at / 吊销后立刻对 MCP 生效(v0.8+ 起 MCP 按 key_id 每次请求现查 KeyStore)。
交易工具的安全边界¶
- unlock_trade 工具不接受密码参数(v1.4+)——
futu_unlock_trade只有unlock: bool入参;密码由服务端从 OS keychain / 环境变量读。LLM prompt 和工具调用日志里永远看不到密码 - simulate 默认 ——
PlaceOrderReq.env默认值是"simulate",LLM 要显式写"real"才走真实账户 - scope 隔离 ——
trade:simulate永远不能下真实单(即使把 env 写成 real 也会被 require_trading 拒) - 限额必过 —— handler 层走 full CheckCtx:market / symbol / value / side / daily 全套检查
- WS 推送防御深度(v1.4+)—— 就算订阅门禁漏了,push 分发前也会按
scope 再过一遍,过滤掉的推送打
futu_ws_push_filtered_total指标
MCP 客户端对接¶
完整教程 → MCP 接入 LLM 包含 Claude Desktop / Cursor / Continue 的配置示例。