1use rmcp::model::ToolAnnotations as RmcpToolAnnotations;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ToolAnnotations {
13 pub read_only: bool,
14 pub destructive: bool,
15 pub idempotent: bool,
16 pub open_world: bool,
17}
18
19impl ToolAnnotations {
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn from_rmcp(rmcp: &RmcpToolAnnotations) -> Self {
26 Self {
27 read_only: rmcp.read_only_hint.unwrap_or(false),
28 destructive: rmcp.destructive_hint.unwrap_or(true),
29 idempotent: rmcp.idempotent_hint.unwrap_or(false),
30 open_world: rmcp.open_world_hint.unwrap_or(true),
31 }
32 }
33
34 pub fn from_rmcp_option(rmcp: Option<&RmcpToolAnnotations>) -> Self {
35 rmcp.map(Self::from_rmcp).unwrap_or_default()
36 }
37
38 #[must_use]
39 pub fn with_read_only(mut self, v: bool) -> Self {
40 self.read_only = v;
41 self
42 }
43
44 #[must_use]
45 pub fn with_destructive(mut self, v: bool) -> Self {
46 self.destructive = v;
47 self
48 }
49
50 #[must_use]
51 pub fn with_open_world(mut self, v: bool) -> Self {
52 self.open_world = v;
53 self
54 }
55
56 #[must_use]
57 pub fn should_require_approval(&self) -> bool {
58 self.destructive && !self.read_only
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum AnnotationType {
65 Destructive,
66 ReadOnly,
67 Idempotent,
68 OpenWorld,
69}
70
71impl AnnotationType {
72 pub fn matches(&self, annotations: &ToolAnnotations) -> bool {
73 match self {
74 AnnotationType::Destructive => annotations.destructive,
75 AnnotationType::ReadOnly => annotations.read_only,
76 AnnotationType::Idempotent => annotations.idempotent,
77 AnnotationType::OpenWorld => annotations.open_world,
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_from_rmcp() {
88 let rmcp = RmcpToolAnnotations {
89 read_only_hint: Some(true),
90 destructive_hint: Some(false),
91 idempotent_hint: Some(true),
92 open_world_hint: Some(false),
93 title: None,
94 };
95 let ann = ToolAnnotations::from_rmcp(&rmcp);
96 assert!(ann.read_only);
97 assert!(!ann.destructive);
98 }
99
100 #[test]
101 fn test_conservative_defaults() {
102 let rmcp = RmcpToolAnnotations {
103 read_only_hint: None,
104 destructive_hint: None,
105 idempotent_hint: None,
106 open_world_hint: None,
107 title: None,
108 };
109 let ann = ToolAnnotations::from_rmcp(&rmcp);
110 assert!(!ann.read_only); assert!(ann.destructive); assert!(!ann.idempotent); assert!(ann.open_world); }
115
116 #[test]
117 fn test_should_require_approval() {
118 assert!(ToolAnnotations::new()
119 .with_destructive(true)
120 .should_require_approval());
121 assert!(!ToolAnnotations::new()
122 .with_destructive(true)
123 .with_read_only(true)
124 .should_require_approval());
125 }
126}