futu_mcp/tools/
algo_order.rs1use std::sync::Arc;
4
5use futu_auth::CheckCtx;
6use futu_net::client::FutuClient;
7use rmcp::{RoleServer, handler::server::wrapper::Parameters, service::RequestContext};
8use serde::Serialize;
9
10use crate::handlers;
11use crate::tool_args::ProtoJsonReq;
12use crate::tool_auth::http_bearer_token;
13
14use super::FutuServer;
15
16async fn call_authenticated_rest<T: Serialize>(
17 base_url: &str,
18 path: &str,
19 bearer: &str,
20 c2s: &T,
21) -> anyhow::Result<String> {
22 super::authenticated_rest::post_json(base_url, path, bearer, c2s, "algorithm request").await
23}
24
25macro_rules! write_tool {
26 ($impl_name:ident, $tool:literal, $label:literal, $path:literal, $c2s:path, $handler:ident, $call:ident) => {
27 impl FutuServer {
28 async fn $impl_name(
29 &self,
30 Parameters(req): Parameters<ProtoJsonReq>,
31 req_ctx: RequestContext<RoleServer>,
32 ) -> std::result::Result<String, String> {
33 let c2s = handlers::proto_json::parse_c2s_json::<$c2s>($label, &req.c2s_json)
34 .map_err(|error| error.to_string())?;
35 let header_token = http_bearer_token(&req_ctx);
36 let override_key = req.api_key.as_deref().or(header_token.as_deref());
37 let caller = self.require_caller_key_strict($tool, override_key)?;
38 let ctx = CheckCtx {
39 market: futu_auth_pipeline::body_aware::trd_market_str(c2s.header.trd_market)
40 .into(),
41 symbol: String::new(),
42 order_value: None,
43 trd_side: None,
44 acc_id: Some(c2s.header.acc_id),
45 mutation_no_exposure: false,
46 currency: None,
47 };
48 if caller.is_some() {
49 if let Some(reject) =
50 self.require_trading_scope_only($tool, "real", caller.as_ref())
51 {
52 return Err(reject);
53 }
54 } else if let Some(reject) =
55 self.require_trading($tool, "real", Some(ctx), override_key)
56 {
57 return Err(reject);
58 }
59 let result = if caller.is_some() {
60 let base_url = self.state.opend_rest_url().ok_or_else(|| {
61 "authenticated algorithm writes require --opend-rest-url (or FUTU_MCP_OPEND_REST_URL)"
62 .to_string()
63 })?;
64 let bearer = override_key
65 .or(self.state.opend_api_key())
66 .ok_or_else(|| "authenticated OpenD REST forwarding lacks a bearer key".to_string())?;
67 call_authenticated_rest(base_url, $path, bearer, &c2s).await
68 } else {
69 let client = self.client_or_err().await?;
70 $call(client, c2s).await
71 };
72 FutuServer::wrap_result(result)
73 }
74 }
75
76 async fn $call(client: Arc<FutuClient>, c2s: $c2s) -> anyhow::Result<String> {
77 handlers::proto_json::$handler(&client, c2s).await
78 }
79 };
80}
81
82write_tool!(
83 futu_place_algo_order_impl,
84 "futu_place_algo_order",
85 "place-algo-order",
86 "/api/trd/place-algo-order",
87 futu_proto::trd_place_algo_order::C2s,
88 place_algo_order,
89 call_place_algo_order
90);
91write_tool!(
92 futu_modify_algo_order_impl,
93 "futu_modify_algo_order",
94 "modify-algo-order",
95 "/api/trd/modify-algo-order",
96 futu_proto::trd_modify_algo_order::C2s,
97 modify_algo_order,
98 call_modify_algo_order
99);
100
101impl FutuServer {
102 async fn futu_algo_order_logs_impl(
103 &self,
104 Parameters(req): Parameters<ProtoJsonReq>,
105 req_ctx: RequestContext<RoleServer>,
106 ) -> std::result::Result<String, String> {
107 let c2s = handlers::proto_json::parse_c2s_json::<futu_proto::trd_get_algo_order_logs::C2s>(
108 "algo-order-logs",
109 &req.c2s_json,
110 )
111 .map_err(|error| error.to_string())?;
112 let client = self
113 .read_client_or_err(
114 "futu_algo_order_logs",
115 &req_ctx,
116 req.api_key.as_deref(),
117 Some(c2s.header.acc_id),
118 )
119 .await?;
120 FutuServer::wrap_result(handlers::proto_json::algo_order_logs(&client, c2s).await)
121 }
122}
123
124include!(concat!(
125 env!("OUT_DIR"),
126 "/generated_mcp_routes_algo_order.rs"
127));