futu_mcp/tool_args/
mod.rs1mod push;
11mod qot;
12mod trd;
13
14pub use push::*;
15pub use qot::*;
16pub use trd::*;
17
18use rmcp::schemars;
19use serde::{Deserialize, Serialize};
20
21use crate::tool_enums::ToolEnum;
22
23#[derive(Debug, Default, Deserialize, schemars::JsonSchema)]
26#[serde(deny_unknown_fields)]
27pub struct NoArgs {}
28
29#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
32#[serde(deny_unknown_fields)]
33pub struct ProtoJsonReq {
34 #[schemars(
35 description = "Official generated C2S JSON. Field names use generated proto serde snake_case, e.g. multi_legs / combo_legs / order_type."
36 )]
37 pub c2s_json: String,
38
39 #[schemars(
40 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."
41 )]
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub api_key: Option<String>,
44}
45
46fn deser_int_or_order_type_str<'de, D>(deserializer: D) -> std::result::Result<i32, D::Error>
61where
62 D: serde::Deserializer<'de>,
63{
64 #[derive(Deserialize)]
65 #[serde(untagged)]
66 enum IntOrStr {
67 Int(i32),
68 Str(String),
69 }
70 match IntOrStr::deserialize(deserializer)? {
71 IntOrStr::Int(i) => Ok(i),
72 IntOrStr::Str(s) => match s.trim().to_ascii_uppercase().as_str() {
73 "NORMAL" | "LIMIT" => Ok(1),
74 "MARKET" => Ok(2),
75 "ABSOLUTE_LIMIT" => Ok(5),
76 "AUCTION" => Ok(6),
77 "AUCTION_LIMIT" => Ok(7),
78 "SPECIAL_LIMIT" => Ok(8),
79 other => other.parse::<i32>().map_err(|_| {
81 serde::de::Error::custom(format!(
82 "unknown order_type {other:?}: expect integer or one of \
83 NORMAL|LIMIT|MARKET|ABSOLUTE_LIMIT|AUCTION|AUCTION_LIMIT|SPECIAL_LIMIT"
84 ))
85 }),
86 },
87 }
88}
89
90fn deser_order_id_raw_from_int_or_str<'de, D>(
97 deserializer: D,
98) -> std::result::Result<String, D::Error>
99where
100 D: serde::Deserializer<'de>,
101{
102 #[derive(Deserialize)]
103 #[serde(untagged)]
104 enum IntOrStr {
105 Int(u64),
106 Str(String),
107 }
108
109 match IntOrStr::deserialize(deserializer)? {
110 IntOrStr::Int(i) => Ok(i.to_string()),
111 IntOrStr::Str(s) => {
112 let trimmed = s.trim();
113 if trimmed.is_empty() {
114 return Err(serde::de::Error::custom(
115 "invalid order_id string: must not be empty",
116 ));
117 }
118 Ok(trimmed.to_string())
119 }
120 }
121}
122
123fn deser_trd_market_string_allow_empty<'de, D>(
132 deserializer: D,
133) -> std::result::Result<String, D::Error>
134where
135 D: serde::Deserializer<'de>,
136{
137 let opt: Option<serde_json::Value> = Option::deserialize(deserializer)?;
138 match opt {
139 None | Some(serde_json::Value::Null) => Ok(String::new()),
140 Some(serde_json::Value::String(s)) if s.trim().is_empty() => Ok(String::new()),
141 Some(v) => {
142 let e = match v {
144 serde_json::Value::Number(n) => {
145 let raw = n.as_i64().ok_or_else(|| {
146 serde::de::Error::custom(format!("trd_market number invalid: {n}"))
147 })?;
148 let i = i32::try_from(raw).map_err(|_| {
149 serde::de::Error::custom(format!(
150 "trd_market number out of i32 range: {raw}"
151 ))
152 })?;
153 crate::tool_enums::TrdMarketEnum::from_i32(i).ok_or_else(|| {
154 serde::de::Error::custom(format!(
155 "unknown trd_market int {i}: valid = {:?}",
156 crate::tool_enums::TrdMarketEnum::all_int_values()
157 ))
158 })?
159 }
160 serde_json::Value::String(s) => {
161 let t = s.trim();
162 crate::tool_enums::TrdMarketEnum::from_str(t)
163 .or_else(|| {
164 t.parse::<i32>()
165 .ok()
166 .and_then(crate::tool_enums::TrdMarketEnum::from_i32)
167 })
168 .ok_or_else(|| {
169 serde::de::Error::custom(format!(
170 "unknown trd_market {s:?}: valid = {:?}",
171 crate::tool_enums::TrdMarketEnum::all_string_values()
172 ))
173 })?
174 }
175 _ => {
176 return Err(serde::de::Error::custom(format!(
177 "trd_market must be int or string, got: {v}"
178 )));
179 }
180 };
181 let i = e.as_i32();
183 let names = crate::tool_enums::TrdMarketEnum::all_string_values();
184 let ints = crate::tool_enums::TrdMarketEnum::all_int_values();
185 let idx = ints
186 .iter()
187 .position(|&v| v == i)
188 .ok_or_else(|| serde::de::Error::custom("trd_market i32 has no canonical"))?;
189 Ok(names[idx].to_string())
190 }
191 }
192}
193
194fn default_kl_type() -> String {
195 "day".to_string()
196}
197
198fn default_depth() -> i32 {
199 10
200}
201
202fn default_ticker_count() -> i32 {
203 100
204}
205
206fn default_plate_set() -> String {
207 "all".to_string()
208}
209
210fn default_env() -> String {
211 "real".to_string()
212}
213
214fn default_env_simulate() -> String {
215 "simulate".to_string()
216}
217
218fn default_order_type() -> String {
219 "NORMAL".to_string()
220}
221
222fn default_modify_op() -> String {
223 "NORMAL".to_string()
224}
225
226fn default_true() -> bool {
227 true
228}
229
230fn default_rehab_none() -> String {
231 "none".to_string()
232}
233
234fn default_history_kline_max_count() -> Option<i32> {
237 Some(1000)
238}
239
240fn default_reference_type() -> String {
241 "warrant".to_string()
243}
244
245fn default_warrant_num() -> i32 {
246 20
247}
248
249fn default_user_security_group_type() -> i32 {
250 1
251}
252
253fn default_stock_filter_num() -> i32 {
254 50
255}
256
257fn default_is_first_push() -> bool {
258 true
259}
260
261fn default_is_reg_push() -> bool {
262 true
263}