Skip to main content

futu_mcp/tools/
system.rs

1//! MCP system/core tools.
2
3use rmcp::{
4    RoleServer, handler::server::wrapper::Parameters, service::RequestContext, tool, tool_router,
5};
6
7use crate::handlers;
8use crate::tool_args::NoArgs;
9
10use super::FutuServer;
11
12pub(crate) fn json_tool_output<T: serde::Serialize>(
13    tool: &str,
14    value: &T,
15) -> std::result::Result<String, String> {
16    serde_json::to_string_pretty(value).map_err(|e| format!("serialize {tool} output: {e}"))
17}
18
19#[tool_router(router = system_tool_router, vis = "pub(crate)")]
20impl FutuServer {
21    #[tool(description = "Ping the Futu gateway. Returns RTT and connection status.")]
22    async fn futu_ping(
23        &self,
24        Parameters(_req): Parameters<NoArgs>,
25        req_ctx: RequestContext<RoleServer>,
26    ) -> std::result::Result<String, String> {
27        // MED-2 修(code review):scope 拒绝是 auth error,用 Err 让 rmcp set
28        // is_error=true,agent 通过 top-level envelope 识别权限问题
29        let _ = self.require_acc_read_with_acc_id("futu_ping", &req_ctx, None, None)?;
30        let gateway = self.state.inner.lock().await.gateway.clone();
31        let client = match self.state.client().await {
32            Ok(c) => c,
33            Err(e) => {
34                tracing::info!(tool = "futu_ping", ok = false, "connect failed: {e}");
35                return json_tool_output(
36                    "futu_ping",
37                    &handlers::core::PingOut {
38                        gateway,
39                        ok: false,
40                        rtt_ms: 0.0,
41                        message: format!("connect failed: {e}"),
42                    },
43                );
44            }
45        };
46        let out = handlers::core::ping(&client, &gateway).await;
47        tracing::info!(tool = "futu_ping", ok = out.ok, rtt_ms = out.rtt_ms);
48        json_tool_output("futu_ping", &out)
49    }
50}