indodax_cli/mcp/
safety.rs1use super::service::ServiceGroup;
2
3#[derive(Debug, Clone)]
5pub struct SafetyConfig {
6 pub allow_dangerous: bool,
9}
10
11impl SafetyConfig {
12 pub fn new(allow_dangerous: bool) -> Self {
17 Self { allow_dangerous }
18 }
19
20 pub fn check_group(&self, group: &ServiceGroup) -> Result<(), String> {
24 if group.is_dangerous() && !self.allow_dangerous {
25 return Err(format!(
26 "[DANGEROUS: requires acknowledged=true] The '{}' service group contains dangerous operations. \
27 Provide `acknowledged: true` in your tool call arguments, or start the MCP server with \
28 `--allow-dangerous` to disable this guard.",
29 group
30 ));
31 }
32 Ok(())
33 }
34
35 pub fn check_operation(&self, group: &ServiceGroup, acknowledged: bool) -> Result<(), String> {
39 if group.is_dangerous() && !self.allow_dangerous && !acknowledged {
40 return Err(format!(
41 "[DANGEROUS: requires acknowledged=true] This operation belongs to the '{}' group. \
42 Set `acknowledged` to `true` to confirm you want to proceed, or start the MCP server \
43 with `--allow-dangerous` to disable this check entirely.",
44 group
45 ));
46 }
47 Ok(())
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn test_safe_group_allowed_with_guarded() {
57 let config = SafetyConfig::new(false);
58 assert!(config.check_group(&ServiceGroup::Market).is_ok());
59 assert!(config.check_group(&ServiceGroup::Account).is_ok());
60 assert!(config.check_group(&ServiceGroup::Paper).is_ok());
61 assert!(config.check_group(&ServiceGroup::Auth).is_ok());
62 assert!(config.check_group(&ServiceGroup::Alert).is_ok());
63 }
64
65 #[test]
66 fn test_dangerous_group_blocked_with_guarded() {
67 let config = SafetyConfig::new(false);
68 assert!(config.check_group(&ServiceGroup::Trade).is_err());
69 assert!(config.check_group(&ServiceGroup::Funding).is_err());
70 }
71
72 #[test]
73 fn test_dangerous_group_allowed_with_allow_dangerous() {
74 let config = SafetyConfig::new(true);
75 assert!(config.check_group(&ServiceGroup::Trade).is_ok());
76 assert!(config.check_group(&ServiceGroup::Funding).is_ok());
77 }
78
79 #[test]
80 fn test_operation_blocked_without_acknowledged() {
81 let config = SafetyConfig::new(false);
82 assert!(config.check_operation(&ServiceGroup::Trade, false).is_err());
83 assert!(config.check_operation(&ServiceGroup::Funding, false).is_err());
84 }
85
86 #[test]
87 fn test_operation_allowed_with_acknowledged() {
88 let config = SafetyConfig::new(false);
89 assert!(config.check_operation(&ServiceGroup::Trade, true).is_ok());
90 assert!(config.check_operation(&ServiceGroup::Funding, true).is_ok());
91 }
92
93 #[test]
94 fn test_safe_operation_no_acknowledgment_needed() {
95 let config = SafetyConfig::new(false);
96 assert!(config.check_operation(&ServiceGroup::Market, false).is_ok());
97 assert!(config.check_operation(&ServiceGroup::Account, false).is_ok());
98 assert!(config.check_operation(&ServiceGroup::Paper, false).is_ok());
99 }
100
101 #[test]
102 fn test_allow_dangerous_overrides_acknowledged() {
103 let config = SafetyConfig::new(true);
104 assert!(config.check_operation(&ServiceGroup::Trade, false).is_ok());
106 assert!(config.check_operation(&ServiceGroup::Funding, false).is_ok());
107 }
108
109 #[test]
110 fn test_error_message_mentions_acknowledged() {
111 let config = SafetyConfig::new(false);
112 let err = config.check_operation(&ServiceGroup::Trade, false).unwrap_err();
113 assert!(err.contains("acknowledged"));
114 assert!(err.contains("true"));
115 }
116}