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 }
63
64 #[test]
65 fn test_dangerous_group_blocked_with_guarded() {
66 let config = SafetyConfig::new(false);
67 assert!(config.check_group(&ServiceGroup::Trade).is_err());
68 assert!(config.check_group(&ServiceGroup::Funding).is_err());
69 }
70
71 #[test]
72 fn test_dangerous_group_allowed_with_allow_dangerous() {
73 let config = SafetyConfig::new(true);
74 assert!(config.check_group(&ServiceGroup::Trade).is_ok());
75 assert!(config.check_group(&ServiceGroup::Funding).is_ok());
76 }
77
78 #[test]
79 fn test_operation_blocked_without_acknowledged() {
80 let config = SafetyConfig::new(false);
81 assert!(config.check_operation(&ServiceGroup::Trade, false).is_err());
82 assert!(config.check_operation(&ServiceGroup::Funding, false).is_err());
83 }
84
85 #[test]
86 fn test_operation_allowed_with_acknowledged() {
87 let config = SafetyConfig::new(false);
88 assert!(config.check_operation(&ServiceGroup::Trade, true).is_ok());
89 assert!(config.check_operation(&ServiceGroup::Funding, true).is_ok());
90 }
91
92 #[test]
93 fn test_safe_operation_no_acknowledgment_needed() {
94 let config = SafetyConfig::new(false);
95 assert!(config.check_operation(&ServiceGroup::Market, false).is_ok());
96 assert!(config.check_operation(&ServiceGroup::Account, false).is_ok());
97 assert!(config.check_operation(&ServiceGroup::Paper, false).is_ok());
98 }
99
100 #[test]
101 fn test_allow_dangerous_overrides_acknowledged() {
102 let config = SafetyConfig::new(true);
103 assert!(config.check_operation(&ServiceGroup::Trade, false).is_ok());
105 assert!(config.check_operation(&ServiceGroup::Funding, false).is_ok());
106 }
107
108 #[test]
109 fn test_error_message_mentions_acknowledged() {
110 let config = SafetyConfig::new(false);
111 let err = config.check_operation(&ServiceGroup::Trade, false).unwrap_err();
112 assert!(err.contains("acknowledged"));
113 assert!(err.contains("true"));
114 }
115}