Skip to content

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_PWD for futu-mcp. Linux / Windows may continue using futucli 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_accounts fields; on partial failure, ok=false explicitly 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 the otp field 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 transport
  • GET /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

  1. Call futu_sub_acc_push. A successful result includes a unique resource_uri, shaped as futu://push/sub-<session-id>, alongside the session_id.
  2. 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.
  3. On a resource-updated notification, call resources/read with the same identity. Each read atomically drains the current queue and reports both events and cumulative dropped_events; the next read contains only newer events.
  4. resources/list returns only resources the current caller can still access. Responses are private with ttlMs=0 and must not be cached.
  5. Call futu_unsub_acc_push to 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

kill -HUP $(pgrep futu-mcp)

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_trade takes no password arg (v1.4+) — futu_unlock_trade only has unlock: 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 defaultPlaceOrderReq.env defaults to "simulate"; the LLM must explicitly pass "real" to hit the real account.
  • Scope isolationtrade:simulate can never place a real order (even if env is spoofed to real, require_trading rejects 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.