mcp_core_rs/
content.rs

1use serde::{Deserialize, Serialize};
2
3use super::{Annotation, ResourceContents, Role};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct TextContent {
8    pub text: String,
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub annotation: Option<Annotation>,
11}
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct ImageContent {
16    pub data: String,
17    pub mime_type: String,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub annotation: Option<Annotation>,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct EmbeddedResource {
25    pub resource: ResourceContents,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub annotation: Option<Annotation>,
28}
29
30impl EmbeddedResource {
31    pub fn get_text(&self) -> String {
32        match &self.resource {
33            ResourceContents::TextResourceContents { text, .. } => text.clone(),
34            _ => String::new(),
35        }
36    }
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[serde(tag = "type", rename_all = "camelCase")]
41pub enum Content {
42    Text(TextContent),
43    Image(ImageContent),
44    Resource(EmbeddedResource),
45}
46
47impl Content {
48    pub fn text<S: Into<String>>(text: S) -> Self {
49        Content::Text(TextContent {
50            text: text.into(),
51            annotation: None,
52        })
53    }
54
55    pub fn image<S: Into<String>, T: Into<String>>(data: S, mime_type: T) -> Self {
56        Content::Image(ImageContent {
57            data: data.into(),
58            mime_type: mime_type.into(),
59            annotation: None,
60        })
61    }
62
63    pub fn resource(resource: ResourceContents) -> Self {
64        Content::Resource(EmbeddedResource {
65            resource,
66            annotation: None,
67        })
68    }
69
70    pub fn embedded_text<S: Into<String>, T: Into<String>>(uri: S, content: T) -> Self {
71        Content::Resource(EmbeddedResource {
72            resource: ResourceContents::TextResourceContents {
73                uri: uri.into(),
74                mime_type: Some("text".to_string()),
75                text: content.into(),
76            },
77            annotation: None,
78        })
79    }
80
81    /// Get the text content if this is a TextContent variant
82    pub fn as_text(&self) -> Option<&str> {
83        match self {
84            Content::Text(text) => Some(&text.text),
85            _ => None,
86        }
87    }
88
89    /// Get the image content if this is an ImageContent variant
90    pub fn as_image(&self) -> Option<(&str, &str)> {
91        match self {
92            Content::Image(image) => Some((&image.data, &image.mime_type)),
93            _ => None,
94        }
95    }
96
97    /// Set the audience for the content
98    pub fn with_audience(mut self, audience: Vec<Role>) -> Self {
99        let annotation = match &mut self {
100            Content::Text(text) => &mut text.annotation,
101            Content::Image(image) => &mut image.annotation,
102            Content::Resource(resource) => &mut resource.annotation,
103        };
104        *annotation = Some(match annotation.take() {
105            Some(mut a) => {
106                a.audience = Some(audience);
107                a
108            }
109            None => Annotation {
110                audience: Some(audience),
111                priority: None,
112                timestamp: None,
113            },
114        });
115        self
116    }
117
118    /// Set the priority for the content
119    /// # Panics
120    /// Panics if priority is not between 0.0 and 1.0 inclusive
121    pub fn with_priority(mut self, priority: f32) -> Self {
122        if !(0.0 ..= 1.0).contains(&priority) {
123            panic!("Priority must be between 0.0 and 1.0");
124        }
125        let annotation = match &mut self {
126            Content::Text(text) => &mut text.annotation,
127            Content::Image(image) => &mut image.annotation,
128            Content::Resource(resource) => &mut resource.annotation,
129        };
130        *annotation = Some(match annotation.take() {
131            Some(mut a) => {
132                a.priority = Some(priority);
133                a
134            }
135            None => Annotation {
136                audience: None,
137                priority: Some(priority),
138                timestamp: None,
139            },
140        });
141        self
142    }
143
144    /// Get the audience if set
145    pub fn audience(&self) -> Option<&Vec<Role>> {
146        match self {
147            Content::Text(text) => text.annotation.as_ref().and_then(|a| a.audience.as_ref()),
148            Content::Image(image) => image.annotation.as_ref().and_then(|a| a.audience.as_ref()),
149            Content::Resource(resource) => resource
150                .annotation
151                .as_ref()
152                .and_then(|a| a.audience.as_ref()),
153        }
154    }
155
156    /// Get the priority if set
157    pub fn priority(&self) -> Option<f32> {
158        match self {
159            Content::Text(text) => text.annotation.as_ref().and_then(|a| a.priority),
160            Content::Image(image) => image.annotation.as_ref().and_then(|a| a.priority),
161            Content::Resource(resource) => resource.annotation.as_ref().and_then(|a| a.priority),
162        }
163    }
164
165    pub fn unannotated(&self) -> Self {
166        match self {
167            Content::Text(text) => Content::text(text.text.clone()),
168            Content::Image(image) => Content::image(image.data.clone(), image.mime_type.clone()),
169            Content::Resource(resource) => Content::resource(resource.resource.clone()),
170        }
171    }
172}