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).
Historical candle selection¶
Since v1.8.4, futu_get_kline interprets count as the latest N candles within the selected date range, matching CLI kline --count N. It consumes subsequent pages, deduplicates by time and returns ascending results. A shorter range returns the available candles. A later-page failure or a cursor cycle returns an error instead of treating an incomplete collection as the latest data.
This tool queries historical data. Its default date range is still estimated from the requested count; provide begin and end for a longer range. Its data source corresponds to Python SDK request_history_kline; use SDK get_cur_kline for current candles after subscribing. Use futu_get_history_kline for explicit page control and rehab selection.
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 backend request) |
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. - Forward risk-confirmation links verbatim (v1.8+) — for some instruments (HK common
stocks, warrants/CBBCs, …) the broker requires the account owner to complete a risk
confirmation first. The order fails and the error text carries a fixed four-line contract:
订单尚未提交,需要先完成风险确认。/风险确认链接:/ the complete HTTPS URL alone on its own line /请由账户本人打开并完成确认。完成后重新提交订单。. When an agent sees this block it means the order was not submitted; the account owner must complete the linked confirmation before submitting again. The agent must relay the original lines and the link verbatim to the user who initiated the trade (not inside a code fence, never summarised as "risk confirmation needed"), then pause the order flow; it must not accept the agreement on the user's behalf, must not retry before the user confirms, and after confirmation must re-check authorization validity, quotes and buying power before placing the order again instead of replaying the old request. The link belongs to the current private interaction only — never write it into logs, test reports or public issues. Errors without that action keep their existing format.
Optional protocol diagnostics (unreleased)¶
The ordinary futu-mcp executable accepts --protocol-diagnostics; it defaults to off and
leaves the default tool catalog unchanged. You can also set FUTU_MCP_PROTOCOL_DIAGNOSTICS=true
or TOML protocol_diagnostics = true. Precedence is CLI, environment, file, then default;
--protocol-diagnostics=false explicitly disables it. New HTTP sessions retain the mode
and have independent legacy logging settings.
| Tool | Protocol behavior |
|---|---|
test_missing_capability |
Requires declared sampling capability, returns typed input_required, validates the client response |
test_streaming_elicitation |
Requests boolean form input through input_required, validates accept / decline / cancel, sends no independent request on the response stream |
test_logging_tool |
Sends a real info notification only when the current 2026 request's logLevel permits it; absent means no notification |
All three take empty arguments and do not contact the gateway or backend. With --keys-file,
each round uses existing HTTP Bearer or stdio startup-key authentication and requires qot:read.
Revoked or restricted keys cannot complete an input round. Sampling and elicitation require
MCP 2026-07-28; missing capabilities return -32021 and HTTP 400. Invalid inputResponses or requestState within a valid request returns JSON-RPC -32602 with HTTP 200; inspect error before treating the call as successful. Use the returned input ID
when resubmitting; malformed responses, extra IDs and requestState are rejected.
Use an isolated loopback process with the pinned official runner, without expected failures:
futu-mcp --protocol-diagnostics --http-listen 127.0.0.1:3333
npx -y @modelcontextprotocol/conformance@0.2.0-alpha.11 server \
--url http://127.0.0.1:3333/mcp --scenario server-stateless --spec-version 2026-07-28
No OpenD login is required. Stop the test process after verification and keep the default configuration for ordinary service use.
MCP client integration¶
See Tutorial: MCP integration for Claude Desktop / Cursor / Continue configuration examples.