mcp_core_rs/
prompt.rs

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