component_shape_mcp/
metadata.rs1use super::*;
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct McpToolIcon {
6 src: &'static str,
7 mime_type: Option<&'static str>,
8 sizes: &'static [&'static str],
9 theme: Option<McpIconTheme>,
10}
11
12impl McpToolIcon {
13 pub const fn new(src: &'static str) -> Self {
15 Self {
16 src,
17 mime_type: None,
18 sizes: &[],
19 theme: None,
20 }
21 }
22
23 pub const fn with_mime_type(mut self, mime_type: &'static str) -> Self {
25 self.mime_type = Some(mime_type);
26 self
27 }
28
29 pub const fn with_sizes(mut self, sizes: &'static [&'static str]) -> Self {
31 self.sizes = sizes;
32 self
33 }
34
35 pub const fn with_theme(mut self, theme: McpIconTheme) -> Self {
37 self.theme = Some(theme);
38 self
39 }
40
41 pub const fn src(self) -> &'static str {
43 self.src
44 }
45
46 pub const fn mime_type(self) -> Option<&'static str> {
48 self.mime_type
49 }
50
51 pub const fn sizes(self) -> &'static [&'static str] {
53 self.sizes
54 }
55
56 pub const fn theme(self) -> Option<McpIconTheme> {
58 self.theme
59 }
60
61 fn into_definition_icon(self) -> McpIcon {
62 let mut icon = McpIcon::new(self.src);
63 if let Some(mime_type) = self.mime_type {
64 icon = icon.with_mime_type(mime_type);
65 }
66 if !self.sizes.is_empty() {
67 icon = icon.with_sizes(self.sizes.iter().map(|size| (*size).to_string()).collect());
68 }
69 if let Some(theme) = self.theme {
70 icon = icon.with_theme(theme);
71 }
72 icon
73 }
74
75 fn validate(self) -> Result<(), McpToolError> {
76 validate_required_metadata_text("icon src", self.src)?;
77 if let Some(mime_type) = self.mime_type {
78 validate_required_metadata_text("icon mime_type", mime_type)?;
79 }
80 for size in self.sizes {
81 validate_required_metadata_text("icon size", size)?;
82 }
83 Ok(())
84 }
85}
86
87#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
89pub struct McpToolMetadata {
90 name: Option<&'static str>,
91 title: Option<&'static str>,
92 description: Option<&'static str>,
93 read_only_hint: Option<bool>,
94 destructive_hint: Option<bool>,
95 idempotent_hint: Option<bool>,
96 open_world_hint: Option<bool>,
97 icons: &'static [McpToolIcon],
98}
99
100impl McpToolMetadata {
101 pub const fn new() -> Self {
103 Self {
104 name: None,
105 title: None,
106 description: None,
107 read_only_hint: None,
108 destructive_hint: None,
109 idempotent_hint: None,
110 open_world_hint: None,
111 icons: &[],
112 }
113 }
114
115 pub const fn with_name(mut self, name: &'static str) -> Self {
117 self.name = Some(name);
118 self
119 }
120
121 pub const fn with_title(mut self, title: &'static str) -> Self {
123 self.title = Some(title);
124 self
125 }
126
127 pub const fn with_description(mut self, description: &'static str) -> Self {
129 self.description = Some(description);
130 self
131 }
132
133 pub const fn with_read_only_hint(mut self, read_only: bool) -> Self {
135 self.read_only_hint = Some(read_only);
136 self
137 }
138
139 pub const fn with_destructive_hint(mut self, destructive: bool) -> Self {
141 self.destructive_hint = Some(destructive);
142 self
143 }
144
145 pub const fn with_idempotent_hint(mut self, idempotent: bool) -> Self {
147 self.idempotent_hint = Some(idempotent);
148 self
149 }
150
151 pub const fn with_open_world_hint(mut self, open_world: bool) -> Self {
153 self.open_world_hint = Some(open_world);
154 self
155 }
156
157 pub const fn with_icons(mut self, icons: &'static [McpToolIcon]) -> Self {
159 self.icons = icons;
160 self
161 }
162
163 pub const fn name(self) -> Option<&'static str> {
165 self.name
166 }
167
168 pub const fn title(self) -> Option<&'static str> {
170 self.title
171 }
172
173 pub const fn description(self) -> Option<&'static str> {
175 self.description
176 }
177
178 pub const fn read_only_hint(self) -> Option<bool> {
180 self.read_only_hint
181 }
182
183 pub const fn destructive_hint(self) -> Option<bool> {
185 self.destructive_hint
186 }
187
188 pub const fn idempotent_hint(self) -> Option<bool> {
190 self.idempotent_hint
191 }
192
193 pub const fn open_world_hint(self) -> Option<bool> {
195 self.open_world_hint
196 }
197
198 pub const fn icons(self) -> &'static [McpToolIcon] {
200 self.icons
201 }
202
203 pub fn tool_annotations(self) -> Option<McpToolAnnotations> {
205 if self.read_only_hint.is_none()
206 && self.destructive_hint.is_none()
207 && self.idempotent_hint.is_none()
208 && self.open_world_hint.is_none()
209 {
210 return None;
211 }
212
213 Some(McpToolAnnotations::from_raw(
214 self.title.map(str::to_string),
215 self.read_only_hint,
216 self.destructive_hint,
217 self.idempotent_hint,
218 self.open_world_hint,
219 ))
220 }
221
222 pub fn tool_icons(self) -> Option<Vec<McpIcon>> {
224 (!self.icons.is_empty()).then(|| {
225 self.icons
226 .iter()
227 .map(|icon| icon.into_definition_icon())
228 .collect()
229 })
230 }
231
232 pub fn validate(self) -> Result<(), McpToolError> {
239 validate_tool_annotation_hints(self.read_only_hint, self.destructive_hint)?;
240 if let Some(name) = self.name {
241 validate_tool_name(name)?;
242 }
243 if let Some(title) = self.title {
244 validate_tool_metadata_text("title", title)?;
245 }
246 if let Some(description) = self.description {
247 validate_tool_metadata_text("description", description)?;
248 }
249 for icon in self.icons {
250 icon.validate()?;
251 }
252 Ok(())
253 }
254}