turbomcp_protocol/types/
content.rs

1//! Message content types
2//!
3//! This module contains all content block types used in MCP messages.
4//! Content blocks allow rich message composition with text, images, audio,
5//! and resource references.
6//!
7//! # Content Types
8//!
9//! - [`ContentBlock`] - Content block enum (text, image, audio, resource link, embedded resource)
10//! - [`TextContent`] - Plain text content with annotations
11//! - [`ImageContent`] - Base64-encoded image content
12//! - [`AudioContent`] - Base64-encoded audio content
13//! - [`ResourceLink`] - Reference to external resource
14//! - [`EmbeddedResource`] - Embedded resource content
15//! - [`ContentType`] - Content type enumeration (JSON/Binary/Text)
16
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19
20use super::core::{Annotations, Base64String, MimeType, Uri};
21
22/// Content type enumeration
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24#[serde(rename_all = "lowercase")]
25pub enum ContentType {
26    /// JSON content
27    Json,
28    /// Binary content
29    Binary,
30    /// Plain text content
31    Text,
32}
33
34/// Content block union type per MCP 2025-06-18 specification
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(tag = "type")]
37pub enum ContentBlock {
38    /// Text content
39    #[serde(rename = "text")]
40    Text(TextContent),
41    /// Image content
42    #[serde(rename = "image")]
43    Image(ImageContent),
44    /// Audio content
45    #[serde(rename = "audio")]
46    Audio(AudioContent),
47    /// Resource link
48    #[serde(rename = "resource_link")]
49    ResourceLink(ResourceLink),
50    /// Embedded resource
51    #[serde(rename = "resource")]
52    Resource(EmbeddedResource),
53}
54
55/// Backward compatibility alias for `ContentBlock`.
56///
57/// The MCP specification originally named this type `Content`, but later renamed it to
58/// `ContentBlock` for clarity. This alias exists to maintain backward compatibility with
59/// code written against earlier versions of the TurboMCP SDK.
60///
61/// **For new code**, prefer using `ContentBlock` directly as it matches the current
62/// MCP specification terminology.
63///
64/// # Example
65///
66/// ```rust
67/// use turbomcp_protocol::types::{Content, ContentBlock, TextContent};
68///
69/// // Both are equivalent:
70/// let content_old: Content = ContentBlock::Text(TextContent {
71///     text: "Hello".to_string(),
72///     annotations: None,
73///     meta: None,
74/// });
75///
76/// let content_new: ContentBlock = ContentBlock::Text(TextContent {
77///     text: "Hello".to_string(),
78///     annotations: None,
79///     meta: None,
80/// });
81/// ```
82pub type Content = ContentBlock;
83
84/// Text content per MCP 2025-06-18 specification
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct TextContent {
87    /// The text content of the message
88    pub text: String,
89    /// Optional annotations for the client
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub annotations: Option<Annotations>,
92    /// General metadata field for extensions and custom data
93    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
94    pub meta: Option<HashMap<String, serde_json::Value>>,
95}
96
97/// Image content per MCP 2025-06-18 specification
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct ImageContent {
100    /// The base64-encoded image data
101    pub data: Base64String,
102    /// The MIME type of the image. Different providers may support different image types
103    #[serde(rename = "mimeType")]
104    pub mime_type: MimeType,
105    /// Optional annotations for the client
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub annotations: Option<Annotations>,
108    /// General metadata field for extensions and custom data
109    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
110    pub meta: Option<HashMap<String, serde_json::Value>>,
111}
112
113/// Audio content per MCP 2025-06-18 specification
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct AudioContent {
116    /// The base64-encoded audio data
117    pub data: Base64String,
118    /// The MIME type of the audio. Different providers may support different audio types
119    #[serde(rename = "mimeType")]
120    pub mime_type: MimeType,
121    /// Optional annotations for the client
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub annotations: Option<Annotations>,
124    /// General metadata field for extensions and custom data
125    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
126    pub meta: Option<HashMap<String, serde_json::Value>>,
127}
128
129/// Resource link per MCP 2025-06-18 specification
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ResourceLink {
132    /// Resource name (programmatic identifier)
133    pub name: String,
134    /// Display title for UI contexts (optional, falls back to name if not provided)
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub title: Option<String>,
137    /// The URI of this resource
138    pub uri: Uri,
139    /// A description of what this resource represents
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub description: Option<String>,
142    /// The MIME type of this resource, if known
143    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
144    pub mime_type: Option<MimeType>,
145    /// Optional annotations for the client
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub annotations: Option<Annotations>,
148    /// The size of the raw resource content, if known
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub size: Option<u64>,
151    /// General metadata field for extensions and custom data
152    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
153    pub meta: Option<HashMap<String, serde_json::Value>>,
154}
155
156/// Embedded resource content per MCP 2025-06-18 specification
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct EmbeddedResource {
159    /// The embedded resource content (text or binary)
160    pub resource: ResourceContent,
161    /// Optional annotations for the client
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub annotations: Option<Annotations>,
164    /// General metadata field for extensions and custom data
165    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
166    pub meta: Option<HashMap<String, serde_json::Value>>,
167}
168
169/// Text resource contents
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct TextResourceContents {
172    /// The URI of this resource
173    pub uri: Uri,
174    /// The MIME type of this resource, if known
175    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
176    pub mime_type: Option<MimeType>,
177    /// The text content (must only be set for text-representable data)
178    pub text: String,
179    /// General metadata field for extensions and custom data
180    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
181    pub meta: Option<HashMap<String, serde_json::Value>>,
182}
183
184/// Binary resource contents
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct BlobResourceContents {
187    /// The URI of this resource
188    pub uri: Uri,
189    /// The MIME type of this resource, if known
190    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
191    pub mime_type: Option<MimeType>,
192    /// Base64-encoded binary data
193    pub blob: Base64String,
194    /// General metadata field for extensions and custom data
195    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
196    pub meta: Option<HashMap<String, serde_json::Value>>,
197}
198
199/// Union type for resource contents (text or binary)
200#[derive(Debug, Clone, Serialize, Deserialize)]
201#[serde(untagged)]
202pub enum ResourceContent {
203    /// Text resource content
204    Text(TextResourceContents),
205    /// Binary resource content
206    Blob(BlobResourceContents),
207}