MCP¶
Model Context Protocol server: exposes gateway tools to LLM clients. Two transports: stdio / HTTP.
Full parameter-level docs for all 60 tools
This page is a tool-list overview. For each tool's complete parameters (v1.4.83/84 aliases + enum dual-accept + deny_unknown_fields + runtime validate) and JSON-RPC invocation examples see API Reference → MCP Tools (new in v1.4.85).
Tool inventory¶
Quote (qot:read)¶
| Tool | Description |
|---|---|
futu_ping |
Ping the gateway, returns RTT |
futu_get_quote |
Real-time quote (auto-subscribes Basic) |
futu_get_snapshot |
Snapshot (52-week high/low, turnover, book) |
futu_get_kline |
Historical candles (day / week / month / 1-60 min) |
futu_get_orderbook |
Level-2 book (auto-subscribes OrderBook) |
futu_get_ticker |
Tick-by-tick trades |
futu_get_rt |
Intraday time series |
futu_get_static |
Static info (name, lot size, list date) |
futu_get_broker |
Broker queue (HK only) |
futu_list_plates |
Plate/sector list |
futu_plate_stocks |
Plate constituents |
futu_get_capital_flow |
v1.4.25+: capital flow time series |
futu_get_capital_distribution |
v1.4.25+: capital distribution (super-large/large/medium/small) |
futu_get_market_state |
v1.4.25+: market state (open/close/lunch break) |
futu_get_history_kline |
v1.4.26+: historical K-line with rehab and max_count |
futu_get_owner_plate |
v1.4.26+: which plates a stock belongs to |
futu_get_reference |
v1.4.26+: related securities (underlying ↔ warrant / future / option) |
futu_get_option_chain |
v1.4.26+: option chain (grouped call/put by expiry) |
futu_get_warrant |
v1.4.29+: warrant list (volume-sorted, optional underlying) |
futu_get_ipo_list |
v1.4.29+: upcoming/recent IPOs by market |
futu_get_future_info |
v1.4.29+: futures contract info (size, last trade date, sessions) |
futu_get_user_security_group |
v1.4.29+: watchlist groups (custom / system) |
futu_get_stock_filter |
v1.4.29+: stock scanner (minimal; advanced filters via REST) |
futu_get_trading_days |
v1.4.30+: trading day list |
futu_get_rehab |
v1.4.30+: rehab factors (long-term K-line alignment / backtest) |
futu_get_suspend |
v1.4.30+: suspension days |
futu_get_user_security |
v1.4.30+: securities in a watchlist group |
futu_get_global_state |
v1.4.30+: gateway global state (market open/close, login, server version) |
futu_get_user_info |
v1.4.30+: user info (per-market quote permissions, subscribe quota) |
futu_get_delay_statistics |
v1.4.30+: delay-statistics summary |
futu_get_history_kl_quota |
v1.4.30+: historical K-line download quota |
futu_get_used_quota |
v1.4.110+: current used subscription and historical K-line quota |
futu_get_holding_change |
v1.4.30+: shareholder changes (executive / institution / fund) |
futu_modify_user_security |
v1.4.30+: modify a watchlist group (add / del / move-out) |
futu_get_code_change |
v1.4.30+: security code changes / temporary tickers (HK for now) |
futu_set_price_reminder |
v1.4.30+: set price reminder |
futu_get_price_reminder |
v1.4.30+: query price reminders |
futu_get_option_expiration_date |
v1.4.30+: option expiry date list |
futu_query_subscription |
v1.4.30+: query current subscriptions |
futu_unsubscribe |
v1.4.30+: unsubscribe market data (supports unsub_all) |
Account read (acc:read)¶
| Tool | Description |
|---|---|
futu_list_accounts |
Trading account list |
futu_get_funds |
Funds summary |
futu_get_positions |
Positions |
futu_get_orders |
Today's orders |
futu_get_deals |
Today's fills |
futu_get_max_trd_qtys |
v1.4.25+: pre-trade max buy/sell qty |
futu_get_order_fee |
v1.4.25+: fee breakdown per order |
futu_get_margin_ratio |
v1.4.25+: margin ratio |
futu_get_history_orders |
v1.4.25+: historical orders |
futu_get_history_deals |
v1.4.25+: historical fills |
futu_sub_acc_push |
v1.4.30+: subscribe account push (orders / fills) |
futu_get_acc_cash_flow |
v1.4.30+: account cash flow statement (by clearing date) |
Trade (trade:real / trade:simulate)¶
| Tool | Description | Extra args |
|---|---|---|
futu_place_order |
Place order | api_key? per-call override |
futu_modify_order |
Modify / cancel | api_key? |
futu_cancel_order |
Cancel (shortcut) | api_key? |
futu_reconfirm_order |
Reconfirm pending order | api_key? |
futu_cancel_all_order |
v1.4.30+: cancel all pending orders (by market or account-wide) | api_key? |
Unlock trading (trade:unlock, v1.4+)¶
| Tool | Description | Extra args |
|---|---|---|
futu_unlock_trade |
Unlock / re-lock real trading | unlock: bool (default true); v1.4.31+ optional otp: String (2FA accounts); v1.4.47+ optional security_firm: i32 (1-7 / FutuHK / hk) to scope to one broker; v1.4.47+ optional acc_ids: Vec<u64> to restrict per-account (intersects with security_firm, resolves shadow sub-account blocking) |
- Plaintext password never enters LLM prompt: the server reads it from
the OS keychain (preferred) or the
FUTU_TRADE_PWDenvironment variable (fallback). - Ops writes the password into the keychain via
futucli set-trade-pwd; the LLM only calls thefutu_unlock_tradetool — password never flows through the model. - MD5 is computed server-side, then sent to the gateway.
- After unlock, the gateway process caches the cipher until the gateway restarts; individual orders don't need to re-unlock.
- v1.4.31+ per-broker unlock + fine-grained response: the response
carries
total_requested/total_unlocked/failed_accountsfields; on partial failure,ok=falseexplicitly lists which accounts didn't unlock (common causes: the account doesn't have trading permission for that market, or it's a "shadow" sub-account). - v1.4.31+ 2FA (dynamic password token) accounts: when the server
returns
need_otp=true, the LLM should prompt the user for the OTP and call again with theotpfield populated.
Startup¶
export FUTU_MCP_API_KEY="fc_xxxx..."
./futu-mcp \
--gateway 127.0.0.1:11111 \
--keys-file ~/.config/futu/keys.json
JSON-RPC frames over stdin/stdout, launched by the LLM client as a child process.
./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 (no token required)GET /.well-known/oauth-protected-resource— OAuth 2.0 Protected Resource Metadata (RFC 9728, v1.4+; lets MCP clients auto-discover scope requirements)
OAuth metadata endpoint (v1.4+)¶
A /mcp request without Authorization: Bearer returns 401 +
WWW-Authenticate: Bearer resource_metadata="/.well-known/oauth-protected-resource".
The client fetches that URL to learn the required scopes:
{
"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/"
}
Why not a full OAuth server?
Futu API keys are manually-configured long-lived credentials — there's
no interactive consent flow. RFC 9728 is designed exactly for
"declare the auth requirement, don't perform the flow" resource
servers. The actual token (= API key) is still handed out out-of-band
by ops and written into the MCP client config's Authorization
header.
Per-call API key precedence (v1.1+)¶
1. `api_key` field in the tool args (explicit)
2. HTTP Authorization: Bearer <token> (HTTP transport only)
3. Startup-time --api-key / FUTU_MCP_API_KEY env var
stdio mode has no HTTP header → falls back 2→3. HTTP mode lets multiple LLMs each carry their own token, with audit / limits tracked independently.
Central scope registry¶
guard::scope_for_tool(name) -> Option<ToolScope> is the single source
of truth. Adding a new #[tool] requires updating this registry —
otherwise the handler returns unknown MCP tool and the
all_known_tools_have_scopes test breaks.
SIGHUP hot reload¶
Changes to a key's scope / limits / expires_at / revocation take effect
immediately on MCP (since v0.8+, MCP looks up KeyStore on every request
by key_id).
Trading-tool security boundary¶
unlock_tradetakes no password arg (v1.4+) —futu_unlock_tradeonly hasunlock: bool; the server reads the password from the OS keychain / env var. The LLM prompt and tool-call log never see the password.- Simulate by default —
PlaceOrderReq.envdefaults to"simulate"; the LLM must explicitly pass"real"to hit the real account. - Scope isolation —
trade:simulatecan never place a real order (even ifenvis spoofed toreal,require_tradingrejects it). - Limits always apply — the handler runs the full CheckCtx: market / symbol / value / side / daily checks.
- WS push defense in depth (v1.4+) — even if the subscription gate
slips, push dispatch runs another scope filter; filtered pushes
increment
futu_ws_push_filtered_total.
MCP client integration¶
See Tutorial: MCP integration for Claude Desktop / Cursor / Continue configuration examples.