Skip to main content

futu_mcp/tools/
algo_order.rs

1//! MCP adapters for guarded v1.8 algorithm orders and execution logs.
2
3use 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));