futu_mcp/tools/
stock_note.rs1use rmcp::{RoleServer, handler::server::wrapper::Parameters, service::RequestContext};
4
5use crate::handlers;
6use crate::tool_args::ProtoJsonReq;
7use crate::tool_auth::http_bearer_token;
8
9use super::FutuServer;
10
11macro_rules! tool {
12 ($method:ident, $tool:literal, $label:literal, $path:literal, $module:ident) => {
13 impl FutuServer {
14 async fn $method(
15 &self,
16 Parameters(req): Parameters<ProtoJsonReq>,
17 req_ctx: RequestContext<RoleServer>,
18 ) -> std::result::Result<String, String> {
19 let c2s = handlers::proto_json::parse_c2s_json::<futu_proto::$module::C2s>(
20 $label,
21 &req.c2s_json,
22 )
23 .map_err(|error| error.to_string())?;
24 let header_token = http_bearer_token(&req_ctx);
25 let override_key = req.api_key.as_deref().or(header_token.as_deref());
26 let caller = self.require_caller_key_strict($tool, override_key)?;
27 let Some(caller) = caller else {
28 return Err("stock-note tools require configured API-key authentication".into());
29 };
30 if !caller.scopes.contains(&futu_auth::Scope::AccRead) {
31 return Err("forbidden".into());
32 }
33 let base_url = self.state.opend_rest_url().ok_or_else(|| {
34 "stock-note tools require --opend-rest-url (or FUTU_MCP_OPEND_REST_URL)"
35 .to_string()
36 })?;
37 let bearer = override_key.or(self.state.opend_api_key()).ok_or_else(|| {
38 "authenticated OpenD REST forwarding lacks a bearer key".to_string()
39 })?;
40 FutuServer::wrap_result(
41 super::authenticated_rest::post_json(
42 base_url,
43 $path,
44 bearer,
45 &c2s,
46 "stock-note read",
47 )
48 .await,
49 )
50 }
51 }
52 };
53}
54
55tool!(
56 futu_get_stock_note_labels_impl,
57 "futu_get_stock_note_labels",
58 "stock-note-labels",
59 "/api/stock-note-labels",
60 qot_get_stock_note_labels
61);
62tool!(
63 futu_get_stock_notes_impl,
64 "futu_get_stock_notes",
65 "stock-notes",
66 "/api/stock-notes",
67 qot_get_stock_notes
68);
69
70impl FutuServer {
71 async fn futu_set_stock_note_impl(
72 &self,
73 Parameters(req): Parameters<ProtoJsonReq>,
74 req_ctx: RequestContext<RoleServer>,
75 ) -> std::result::Result<String, String> {
76 let c2s = handlers::proto_json::parse_c2s_json::<futu_proto::qot_set_stock_note::C2s>(
77 "set-stock-note",
78 &req.c2s_json,
79 )
80 .map_err(|error| error.to_string())?;
81 let header_token = http_bearer_token(&req_ctx);
82 let override_key = req.api_key.as_deref().or(header_token.as_deref());
83 let caller = self.require_caller_key_strict("futu_set_stock_note", override_key)?;
84 let Some(caller) = caller else {
85 return Err("stock-note tools require configured API-key authentication".into());
86 };
87 if !caller.scopes.contains(&futu_auth::Scope::QotWrite) {
88 return Err("forbidden".into());
89 }
90 let base_url = self.state.opend_rest_url().ok_or_else(|| {
91 "stock-note tools require --opend-rest-url (or FUTU_MCP_OPEND_REST_URL)".to_string()
92 })?;
93 let bearer = override_key
94 .or(self.state.opend_api_key())
95 .ok_or_else(|| "authenticated OpenD REST forwarding lacks a bearer key".to_string())?;
96 FutuServer::wrap_result(
97 super::authenticated_rest::post_json(
98 base_url,
99 "/api/set-stock-note",
100 bearer,
101 &c2s,
102 "stock-note write",
103 )
104 .await,
105 )
106 }
107}
108
109include!(concat!(
110 env!("OUT_DIR"),
111 "/generated_mcp_routes_stock_note.rs"
112));
113
114#[cfg(test)]
115#[path = "stock_note_authenticated_rest_tests.rs"]
116mod authenticated_rest_tests;