mcp_host/content/
types.rs1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use super::annotations::Annotations;
9
10pub trait Content: Send + Sync {
12 fn content_type(&self) -> &'static str;
14
15 fn to_value(&self) -> Value;
17
18 fn annotations(&self) -> Option<&Annotations>;
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24pub struct TextContent {
25 #[serde(rename = "type")]
27 pub r#type: String,
28
29 pub text: String,
31
32 #[serde(skip_serializing_if = "Option::is_none", flatten)]
34 pub annotations: Option<Annotations>,
35}
36
37impl TextContent {
38 pub fn new(text: impl Into<String>) -> Self {
40 Self {
41 r#type: "text".to_string(),
42 text: text.into(),
43 annotations: None,
44 }
45 }
46
47 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
49 self.annotations = Some(annotations);
50 self
51 }
52}
53
54impl Content for TextContent {
55 fn content_type(&self) -> &'static str {
56 "text"
57 }
58
59 fn to_value(&self) -> Value {
60 serde_json::to_value(self).unwrap()
61 }
62
63 fn annotations(&self) -> Option<&Annotations> {
64 self.annotations.as_ref()
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
70pub struct ImageContent {
71 #[serde(rename = "type")]
73 pub r#type: String,
74
75 pub data: String,
77
78 #[serde(rename = "mimeType")]
80 pub mime_type: String,
81
82 #[serde(skip_serializing_if = "Option::is_none", flatten)]
84 pub annotations: Option<Annotations>,
85}
86
87impl ImageContent {
88 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
90 Self {
91 r#type: "image".to_string(),
92 data: data.into(),
93 mime_type: mime_type.into(),
94 annotations: None,
95 }
96 }
97
98 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
100 self.annotations = Some(annotations);
101 self
102 }
103}
104
105impl Content for ImageContent {
106 fn content_type(&self) -> &'static str {
107 "image"
108 }
109
110 fn to_value(&self) -> Value {
111 serde_json::to_value(self).unwrap()
112 }
113
114 fn annotations(&self) -> Option<&Annotations> {
115 self.annotations.as_ref()
116 }
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
121pub struct AudioContent {
122 #[serde(rename = "type")]
124 pub r#type: String,
125
126 pub data: String,
128
129 #[serde(rename = "mimeType")]
131 pub mime_type: String,
132
133 #[serde(skip_serializing_if = "Option::is_none", flatten)]
135 pub annotations: Option<Annotations>,
136}
137
138impl AudioContent {
139 pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
141 Self {
142 r#type: "audio".to_string(),
143 data: data.into(),
144 mime_type: mime_type.into(),
145 annotations: None,
146 }
147 }
148
149 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
151 self.annotations = Some(annotations);
152 self
153 }
154}
155
156impl Content for AudioContent {
157 fn content_type(&self) -> &'static str {
158 "audio"
159 }
160
161 fn to_value(&self) -> Value {
162 serde_json::to_value(self).unwrap()
163 }
164
165 fn annotations(&self) -> Option<&Annotations> {
166 self.annotations.as_ref()
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
172pub struct ResourceLink {
173 #[serde(rename = "type")]
175 pub r#type: String,
176
177 pub uri: String,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub title: Option<String>,
183
184 #[serde(skip_serializing_if = "Option::is_none", flatten)]
186 pub annotations: Option<Annotations>,
187}
188
189impl ResourceLink {
190 pub fn new(uri: impl Into<String>) -> Self {
192 Self {
193 r#type: "resource".to_string(),
194 uri: uri.into(),
195 title: None,
196 annotations: None,
197 }
198 }
199
200 pub fn with_title(mut self, title: impl Into<String>) -> Self {
202 self.title = Some(title.into());
203 self
204 }
205
206 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
208 self.annotations = Some(annotations);
209 self
210 }
211}
212
213impl Content for ResourceLink {
214 fn content_type(&self) -> &'static str {
215 "resource"
216 }
217
218 fn to_value(&self) -> Value {
219 serde_json::to_value(self).unwrap()
220 }
221
222 fn annotations(&self) -> Option<&Annotations> {
223 self.annotations.as_ref()
224 }
225}
226
227pub type ContentArray = Vec<Box<dyn Content>>;
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_text_content() {
236 let content = TextContent::new("Hello, world!");
237 assert_eq!(content.content_type(), "text");
238 assert_eq!(content.text, "Hello, world!");
239 assert!(content.annotations.is_none());
240 }
241
242 #[test]
243 fn test_text_with_annotations() {
244 let ann = Annotations::new().with_priority(0.9);
245 let content = TextContent::new("Important!").with_annotations(ann.clone());
246
247 assert_eq!(content.annotations, Some(ann));
248 }
249
250 #[test]
251 fn test_image_content() {
252 let content = ImageContent::new("base64data", "image/png");
253 assert_eq!(content.content_type(), "image");
254 assert_eq!(content.data, "base64data");
255 assert_eq!(content.mime_type, "image/png");
256 }
257
258 #[test]
259 fn test_resource_link() {
260 let link = ResourceLink::new("file://test.txt").with_title("Test File");
261
262 assert_eq!(link.content_type(), "resource");
263 assert_eq!(link.uri, "file://test.txt");
264 assert_eq!(link.title, Some("Test File".to_string()));
265 }
266
267 #[test]
268 fn test_serialization() {
269 let content = TextContent::new("test");
270 let json = content.to_value();
271
272 assert_eq!(json["text"], "test");
273 assert_eq!(json["type"], "text");
274 }
275}