Skip to main content

futu_mcp/tools/
reference_price_reminder.rs

1//! MCP price-reminder and option-expiration reference tools.
2
3use crate::handlers;
4use crate::tool_args::*;
5use rmcp::{
6    RoleServer, handler::server::wrapper::Parameters, service::RequestContext, tool, tool_router,
7};
8
9use super::FutuServer;
10
11#[tool_router(router = reference_price_reminder_tool_router, vis = "pub(crate)")]
12impl FutuServer {
13    #[tool(description = "Set price reminder. `op` accepts integer code 1-6 \
14                       (1=Add, 2=Del, 3=Enable, 4=Disable, 5=Modify, 6=DeleteAll); \
15                       MCP also accepts string aliases Add/Del/Enable/Disable/Modify/DeleteAll \
16                       (and legacy SetAdd/SetDel/SetEnable/SetDisable/DelAll). \
17                       Add (op=1) requires reminder_type + freq + value. \
18                       Modify (op=5) requires key (other fields preserved when omitted). \
19                       Python SDK: OpenQuoteContext.set_price_reminder.")]
20    async fn futu_set_price_reminder(
21        &self,
22        Parameters(req): Parameters<SetPriceReminderReq>,
23        req_ctx: RequestContext<RoleServer>,
24    ) -> std::result::Result<String, String> {
25        // v1.4.104 codex round 1 F2 (P1) fix: 改 caller-specific scope check
26        // (之前 require_tool_scope 只看 startup key, HTTP Bearer 调用方权限
27        // 没真核, narrow Bearer 仍能修改 / 删除全局 price reminder 状态).
28        // v1.4.84 §5 B4: op-conditional required field check
29        req.validate()?;
30        tracing::info!(
31            tool = "futu_set_price_reminder",
32            symbol = %req.symbol,
33            op = req.op
34        );
35        let client = self
36            .read_client_or_err("futu_set_price_reminder", &req_ctx, None, None)
37            .await?;
38        Self::wrap_result(
39            handlers::reference::set_price_reminder(
40                &client,
41                handlers::reference::SetPriceReminderInput {
42                    symbol: &req.symbol,
43                    op: req.op,
44                    key: req.key,
45                    reminder_type: req.reminder_type,
46                    freq: req.freq,
47                    value: req.value,
48                    note: req.note.as_deref(),
49                    reminder_session_list: &req.reminder_session_list,
50                },
51            )
52            .await,
53        )
54    }
55
56    #[tool(
57        description = "Query price reminders (by symbol or market). Python SDK: OpenQuoteContext.get_price_reminder."
58    )]
59    async fn futu_get_price_reminder(
60        &self,
61        Parameters(req): Parameters<GetPriceReminderReq>,
62        req_ctx: RequestContext<RoleServer>,
63    ) -> std::result::Result<String, String> {
64        // v1.4.84 §5 B4: symbol XOR market required
65        req.validate()?;
66        tracing::info!(
67            tool = "futu_get_price_reminder",
68            // v1.4.90 P2-C: Option<String> / Option<i32> 都 flatten,避免 record_debug 编 string
69            symbol = %crate::state::audit_fmt::opt_str(req.symbol.as_deref()),
70            market = crate::state::audit_fmt::opt_i32(req.market)
71        );
72        let client = self
73            .read_client_or_err("futu_get_price_reminder", &req_ctx, None, None)
74            .await?;
75        Self::wrap_result(
76            handlers::reference::get_price_reminder(&client, req.symbol.as_deref(), req.market)
77                .await,
78        )
79    }
80
81    #[tool(
82        description = "Option expiration-date list for an underlying (HSI / HSCEI or HK/US equity). Python SDK: OpenQuoteContext.get_option_expiration_date."
83    )]
84    async fn futu_get_option_expiration_date(
85        &self,
86        Parameters(req): Parameters<OptionExpirationDateReq>,
87        req_ctx: RequestContext<RoleServer>,
88    ) -> std::result::Result<String, String> {
89        tracing::info!(
90            tool = "futu_get_option_expiration_date",
91            owner = %req.owner_symbol
92        );
93        let client = self
94            .read_client_or_err("futu_get_option_expiration_date", &req_ctx, None, None)
95            .await?;
96        Self::wrap_result(
97            handlers::reference::get_option_expiration_date(
98                &client,
99                &req.owner_symbol,
100                req.index_option_type,
101            )
102            .await,
103        )
104    }
105}