Skip to main content

futu_mcp/tool_args/
push.rs

1//! v1.4.110 P1-1: 拆自 `tool_args.rs` 按 handler 域分组.
2
3use rmcp::schemars;
4use serde::Deserialize;
5
6use crate::tool_enums;
7
8use super::*;
9
10fn sub_type_list_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
11    schemars::json_schema!({
12        "type": "array",
13        "items": {
14            "oneOf": [
15                { "type": "integer", "format": "int32" },
16                { "type": "string" }
17            ]
18        }
19    })
20}
21
22#[derive(Debug, Deserialize, schemars::JsonSchema)]
23#[serde(deny_unknown_fields)]
24pub struct QuerySubscriptionReq {
25    #[schemars(description = "true=query all connections; false=only this connection (default)")]
26    #[serde(default)]
27    pub is_req_all_conn: bool,
28}
29
30#[derive(Debug, Deserialize, schemars::JsonSchema)]
31#[serde(deny_unknown_fields)]
32pub struct UnsubscribeReq {
33    #[schemars(
34        description = "Security symbols to unsubscribe (ignored if unsub_all=true); alias: stocks / code_list / symbol_list / security_list"
35    )]
36    // v1.4.84 §5 B1
37    #[serde(
38        default,
39        alias = "stocks",
40        alias = "code_list",
41        alias = "symbol_list",
42        alias = "security_list"
43    )]
44    pub symbols: Vec<String>,
45    #[schemars(
46        description = "Sub-type ids to unsubscribe. Accept int (1=Basic, 2=OrderBook, 4=Ticker, \
47                       5=RT, 6=KL_Day, 7=KL_5Min, 8=KL_15Min, 9=KL_30Min, 10=KL_60Min, 11=KL_1Min, \
48                       12=KL_Week, 13=KL_Month, 14=Broker, 15=KL_Quarter, 16=KL_Year, 17=KL_3Min, \
49                       18=KL_10Min, 19=KL_120Min, 20=KL_180Min, 21=KL_240Min, 22=OrderBook_Odd) \
50                       OR string (\"Basic\" / \"OrderBook\" / \"KL_Day\" / \"day\" / ...). Alias: \
51                       sub_type_list. Uses the daemon proto mapping: 3 is reserved/None, 4=Ticker, \
52                       10=KL_60Min, 13=KL_Month, 18=KL_10Min."
53    )]
54    #[schemars(schema_with = "sub_type_list_schema")]
55    // v1.4.84 §5 B2 field migration: Vec<SubTypeEnum> 双接
56    #[serde(
57        default,
58        alias = "sub_type_list",
59        deserialize_with = "tool_enums::deser_subtype_list_as_vec_i32"
60    )]
61    pub sub_types: Vec<i32>,
62    #[schemars(
63        description = "true=clear all subscriptions on this connection (ignores symbols/sub_types); alias: unsubscribe_all"
64    )]
65    #[serde(default, alias = "unsubscribe_all")]
66    pub unsub_all: bool,
67}
68
69/// Request to subscribe to quote streams for one or more securities.
70#[derive(Debug, Deserialize, schemars::JsonSchema)]
71#[serde(deny_unknown_fields)]
72pub struct SubscribeReq {
73    #[schemars(
74        description = "Security symbols to subscribe, e.g. [\"HK.00700\", \"US.AAPL\"]. Alias: stocks / code_list / symbol_list / security_list"
75    )]
76    // v1.4.84 §5 B1
77    #[serde(
78        alias = "stocks",
79        alias = "code_list",
80        alias = "symbol_list",
81        alias = "security_list"
82    )]
83    pub symbols: Vec<String>,
84    #[schemars(
85        description = "Sub-type ids to subscribe. Accept int (1=Basic, 2=OrderBook, 4=Ticker, \
86                       5=RT, 6=KL_Day, 7=KL_5Min, 8=KL_15Min, 9=KL_30Min, 10=KL_60Min, 11=KL_1Min, \
87                       12=KL_Week, 13=KL_Month, 14=Broker, 15=KL_Quarter, 16=KL_Year, 17=KL_3Min, \
88                       18=KL_10Min, 19=KL_120Min, 20=KL_180Min, 21=KL_240Min, 22=OrderBook_Odd) \
89                       OR string (\"Basic\" / \"OrderBook\" / \"KL_Day\" / \"day\" / ...). Alias: \
90                       sub_type_list. Uses the daemon proto mapping: 3 is reserved/None, 4=Ticker, \
91                       10=KL_60Min, 13=KL_Month, 18=KL_10Min."
92    )]
93    #[schemars(schema_with = "sub_type_list_schema")]
94    // v1.4.84 §5 B2 field migration
95    #[serde(
96        alias = "sub_type_list",
97        deserialize_with = "tool_enums::deser_subtype_list_as_vec_i32"
98    )]
99    pub sub_types: Vec<i32>,
100    #[schemars(
101        description = "If true, backend pushes current snapshot immediately after subscribe (useful for agents needing warm state). Default true."
102    )]
103    #[serde(default = "default_is_first_push")]
104    pub is_first_push: bool,
105    #[schemars(
106        description = "If true, register push on this connection (agent will receive push via SSE notification in HTTP mode). Default true."
107    )]
108    #[serde(default = "default_is_reg_push")]
109    pub is_reg_push: bool,
110    #[schemars(
111        description = "Qot_Sub.extendedTime: include US pre/post-market data for supported real-time K/RT/Ticker subscriptions. Default false."
112    )]
113    #[serde(default, alias = "extendedTime")]
114    pub extended_time: Option<bool>,
115    #[schemars(
116        description = "Session: 0=NONE, 1=RTH, 2=ETH, 3=ALL. OVERNIGHT is not supported for subscriptions."
117    )]
118    #[serde(default)]
119    pub session: Option<i32>,
120    #[schemars(
121        description = "Qot_Sub.isSubOrderBookDetail: subscribe order-book detail when available. Default false."
122    )]
123    #[serde(
124        default,
125        alias = "is_sub_order_book_detail",
126        alias = "orderbook_detail"
127    )]
128    pub is_sub_order_book_detail: Option<bool>,
129}
130
131impl SubscribeReq {
132    pub fn validate(&self) -> Result<(), String> {
133        futu_surface_spec::input::validate_subscription_session_id(self.session)
134            .map_err(|error| format!("SubscribeReq.session invalid: {error}"))?;
135        match futu_core::qot_subscription_options::SubscribeOptionsPlan::from_raw(
136            self.session,
137            self.extended_time,
138            self.is_sub_order_book_detail,
139        ) {
140            Ok(_) => Ok(()),
141            Err(futu_core::qot_subscription_options::SubscribePlanError::OvernightSessionUnsupported) => {
142                Err(
143                    "SubscribeReq.session=4 (OVERNIGHT) is not supported by QOT subscribe; \
144                 use 2=ETH or 3=ALL"
145                        .to_string(),
146                )
147            }
148        }
149    }
150}
151
152#[derive(Debug, Deserialize, schemars::JsonSchema)]
153#[serde(deny_unknown_fields)]
154pub struct SubAccPushReq {
155    #[schemars(description = "Array of account IDs (u64) to receive order/deal push \
156                       for. ⚠️ Call `futu_list_accounts` first to discover real \
157                       `acc_id` values; do NOT hallucinate 18-digit numbers — \
158                       invalid ids will silently fail to receive push. Alias: account_ids / accounts")]
159    // v1.4.84 §5 B1
160    #[serde(alias = "account_ids", alias = "accounts")]
161    pub acc_ids: Vec<u64>,
162    #[schemars(
163        description = "Optional per-call API key plaintext. HTTP scope mode still requires a valid Bearer on every /mcp request; this field overrides that identity for the tool handler. In stdio mode: tool argument > startup key."
164    )]
165    #[serde(default)]
166    pub api_key: Option<String>,
167}
168
169/// Request to remove a previously created account-push subscription.
170#[derive(Debug, Deserialize, schemars::JsonSchema)]
171#[serde(deny_unknown_fields)]
172pub struct UnsubAccPushReq {
173    /// session_id from previous `futu_sub_acc_push` response — **required**.
174    /// rmcp 的 Peer<RoleServer> 不实装 PartialEq,无法按 peer 身份批量撤销。
175    #[schemars(description = "Required: session_id returned by `futu_sub_acc_push` \
176                       response (session_id field or unsub_hint). If omitted, \
177                       handler returns an error. If session_id not found (e.g. \
178                       4h auto-purged), removed_count=0 is returned.")]
179    #[serde(default)]
180    pub session_id: Option<String>,
181    #[schemars(
182        description = "Optional per-call API key plaintext. HTTP scope mode still requires a valid Bearer on every /mcp request; this field overrides that identity for the tool handler. In stdio mode use the same tool/startup key that created the subscription."
183    )]
184    #[serde(default)]
185    pub api_key: Option<String>,
186}