mcp_spec/
prompt.rs

1use crate::content::{Annotations, EmbeddedResource, ImageContent};
2use crate::handler::PromptError;
3use crate::resource::ResourceContents;
4use base64::engine::{general_purpose::STANDARD as BASE64_STANDARD, Engine};
5use serde::{Deserialize, Serialize};
6
7/// A prompt that can be used to generate text from a model
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Prompt {
11    /// The name of the prompt
12    pub name: String,
13    /// Optional description of what the prompt does
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub description: Option<String>,
16    /// Optional arguments that can be passed to customize the prompt
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub arguments: Option<Vec<PromptArgument>>,
19}
20
21impl Prompt {
22    /// Create a new prompt with the given name, description and arguments
23    pub fn new<N, D>(
24        name: N,
25        description: Option<D>,
26        arguments: Option<Vec<PromptArgument>>,
27    ) -> Self
28    where
29        N: Into<String>,
30        D: Into<String>,
31    {
32        Prompt {
33            name: name.into(),
34            description: description.map(Into::into),
35            arguments,
36        }
37    }
38}
39
40/// Represents a prompt argument that can be passed to customize the prompt
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct PromptArgument {
43    /// The name of the argument
44    pub name: String,
45    /// A description of what the argument is used for
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub description: Option<String>,
48    /// Whether this argument is required
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub required: Option<bool>,
51}
52
53/// Represents the role of a message sender in a prompt conversation
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum PromptMessageRole {
57    User,
58    Assistant,
59}
60
61/// Content types that can be included in prompt messages
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[serde(tag = "type", rename_all = "lowercase")]
64pub enum PromptMessageContent {
65    /// Plain text content
66    Text { text: String },
67    /// Image content with base64-encoded data
68    Image { image: ImageContent },
69    /// Embedded server-side resource
70    Resource { resource: EmbeddedResource },
71}
72
73/// A message in a prompt conversation
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct PromptMessage {
76    /// The role of the message sender
77    pub role: PromptMessageRole,
78    /// The content of the message
79    pub content: PromptMessageContent,
80}
81
82impl PromptMessage {
83    /// Create a new text message with the given role and text content
84    pub fn new_text<S: Into<String>>(role: PromptMessageRole, text: S) -> Self {
85        Self {
86            role,
87            content: PromptMessageContent::Text { text: text.into() },
88        }
89    }
90
91    pub fn new_image<S: Into<String>>(
92        role: PromptMessageRole,
93        data: S,
94        mime_type: S,
95        annotations: Option<Annotations>,
96    ) -> Result<Self, PromptError> {
97        let data = data.into();
98        let mime_type = mime_type.into();
99
100        // Validate base64 data
101        BASE64_STANDARD.decode(&data).map_err(|_| {
102            PromptError::InvalidParameters("Image data must be valid base64".to_string())
103        })?;
104
105        // Validate mime type
106        if !mime_type.starts_with("image/") {
107            return Err(PromptError::InvalidParameters(
108                "MIME type must be a valid image type (e.g. image/jpeg)".to_string(),
109            ));
110        }
111
112        Ok(Self {
113            role,
114            content: PromptMessageContent::Image {
115                image: ImageContent {
116                    data,
117                    mime_type,
118                    annotations,
119                },
120            },
121        })
122    }
123
124    /// Create a new resource message
125    pub fn new_resource(
126        role: PromptMessageRole,
127        uri: String,
128        mime_type: String,
129        text: Option<String>,
130        annotations: Option<Annotations>,
131    ) -> Self {
132        let resource_contents = ResourceContents::TextResourceContents {
133            uri,
134            mime_type: Some(mime_type),
135            text: text.unwrap_or_default(),
136        };
137
138        Self {
139            role,
140            content: PromptMessageContent::Resource {
141                resource: EmbeddedResource {
142                    resource: resource_contents,
143                    annotations,
144                },
145            },
146        }
147    }
148}
149
150/// A template for a prompt
151#[derive(Debug, Serialize, Deserialize)]
152pub struct PromptTemplate {
153    pub id: String,
154    pub template: String,
155    pub arguments: Vec<PromptArgumentTemplate>,
156}
157
158/// A template for a prompt argument, this should be identical to PromptArgument
159#[derive(Debug, Serialize, Deserialize)]
160pub struct PromptArgumentTemplate {
161    pub name: String,
162    pub description: Option<String>,
163    pub required: Option<bool>,
164}