MCP¶
Model Context Protocol server: exposes gateway tools to LLM clients. Two transports: stdio / HTTP.
Full parameter-level docs for all 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 HK/US/CN/SG/JP/MY market |
futu_get_ipo_calendar |
v1.5.3+: calendar projection over the IPO list (listing / subscription / winning / JP inquiry events) |
futu_get_financial_calendar |
v1.5.3+: all-market financial calendar view with market, date, event-type, and mobile calendar filters |
futu_search_target_financial_calendar |
v1.5.3+: target-stock financial calendar lookup by stock id (mobile/moomoo CMD20426) |
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 used / remaining / total download quota, with a source note |
futu_get_used_quota |
v1.5.3+: 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 the LLM prompt: the server first reads
FUTU_TRADE_PWD(explicit runtime override, trimmed), then performs a bounded compatibility read from the account-scoped / legacy OS credential store. - Packaged macOS binaries have distinct ad-hoc signing identities and cannot
reliably share unattended Keychain items; set
FUTU_TRADE_PWDforfutu-mcp. Linux / Windows may continue usingfutucli set-trade-pwd. Credential-store reads have a three-second safety boundary. - 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)
Protocol versions and lifecycles (v1.6+)¶
stdio and streamable HTTP (POST /mcp) share this version contract:
| MCP protocol date | Lifecycle | Server capabilities | Account push delivery |
|---|---|---|---|
2024-11-05 |
legacy initialize |
tools + logging | logging notification |
2025-03-26 |
legacy initialize |
tools + logging | logging notification |
2025-06-18 |
legacy initialize |
tools + logging | logging notification |
2025-11-25 |
legacy initialize |
tools + logging | logging notification |
2026-07-28 |
modern discover |
tools; add resources/subscriptions only for a reusable acc:read identity |
private push resource (conditional) |
Representative rmcp 1.5.0 and 2.2.0 clients are exercised against real
futu-mcp processes over both stdio and HTTP. This proves those representative
versions, not every third-party MCP SDK. HTTP exposes /mcp only; the legacy
HTTP+SSE /sse endpoint is not supported.
2026 discovery projects capabilities for the current caller. Resources are
advertised only when the HTTP Bearer or stdio startup key still passes machine
binding, expiry, and acc:read; no-key or insufficient-scope callers remain
tools-only, and unadvertised resource methods fail closed.
Reading account pushes with 2026-07-28¶
- Call
futu_sub_acc_push. A successful result includes a uniqueresource_uri, shaped asfutu://push/sub-<session-id>, alongside thesession_id. - With the same reusable API-key identity, call
subscriptions/listen; its filter must contain exactly that canonical URI. The namespace acknowledgement accepts only the filter. Owner/scope/TTL authorization follows; rejection ends with a final error and never attaches a listener. - On a resource-updated notification, call
resources/readwith the same identity. Each read atomically drains the current queue and reports botheventsand cumulativedropped_events; the next read contains only newer events. resources/listreturns only resources the current caller can still access. Responses are private withttlMs=0and must not be cached.- Call
futu_unsub_acc_pushto clean up explicitly. Otherwise a handle lives for at most four hours.
Each resource has a 64-event FIFO. A slow consumer drops the oldest event and
increments dropped_events. The process admits at most 128 modern handles;
capacity rejection happens before any gateway request. Revoked/expired keys,
failed machine binding, or narrower account/market scopes make list/listen/read
fail closed and do not expose the queued events to another identity.
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.
API key sources and precedence (v1.1+)¶
HTTP transport: every /mcp request requires Authorization: Bearer <token> (no startup-key fallback)
stdio transport: schema-declared api_key > startup --api-key / FUTU_MCP_API_KEY
The Authorization header is mandatory on every HTTP transport request;
initialize, tools/list, tool calls, and session DELETE are authenticated
before an MCP session is created or accessed. Startup --api-key /
FUTU_MCP_API_KEY is stdio-only and never substitutes for an HTTP Bearer.
api_key is not an implicit field shared by every tool: only tools
whose reference and schema explicitly list it accept an argument-level
override. HTTP mode lets multiple LLMs carry separate Bearer tokens, with audit
and 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.