跳转至

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 transport
  • GET /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 热重载

kill -HUP $(pgrep futu-mcp)

改了 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 的配置示例。