Skip to content
2 changes: 1 addition & 1 deletion crates/rmcp/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ impl ProtocolVersion {
pub const V_2025_06_18: Self = Self(Cow::Borrowed("2025-06-18"));
pub const V_2025_03_26: Self = Self(Cow::Borrowed("2025-03-26"));
pub const V_2024_11_05: Self = Self(Cow::Borrowed("2024-11-05"));
pub const LATEST: Self = Self::V_2025_03_26;
pub const LATEST: Self = Self::V_2025_06_18;
Comment thread
aharvard marked this conversation as resolved.
Outdated
}

impl Serialize for ProtocolVersion {
Expand Down
18 changes: 9 additions & 9 deletions crates/rmcp/src/model/annotated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub struct Annotations {
pub audience: Option<Vec<Role>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", rename = "lastModified")]
pub last_modified: Option<DateTime<Utc>>,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was curious about this, I asked Goose, why did we change the timestamp to last_modified?

Short answer: to match the spec and be precise.

Details:

Spec alignment: The current MCP spec uses lastModified (camelCase on the wire) to mean “when this content/resource was last updated.” Our previous timestamp field was an SDK-ism and didn’t match the spec name.

Clearer semantics: timestamp was ambiguous (creation time? event time?); lastModified explicitly conveys modification time, which also aligns with common HTTP/metadata semantics.

Cross‑SDK consistency: Other SDKs and examples use lastModified, so this keeps the Rust SDK consistent.

Rust naming: In Rust we expose last_modified and serialize as lastModified to conform to the JSON field name.

}

impl Annotations {
Expand All @@ -30,7 +30,7 @@ impl Annotations {
);
Annotations {
priority: Some(priority),
timestamp: Some(timestamp),
last_modified: Some(timestamp),
audience: None,
}
}
Expand Down Expand Up @@ -72,7 +72,7 @@ impl<T: AnnotateAble> Annotated<T> {
self.annotations.as_ref().and_then(|a| a.priority)
}
pub fn timestamp(&self) -> Option<DateTime<Utc>> {
self.annotations.as_ref().and_then(|a| a.timestamp)
self.annotations.as_ref().and_then(|a| a.last_modified)
}
pub fn with_audience(self, audience: Vec<Role>) -> Annotated<T>
where
Expand All @@ -92,7 +92,7 @@ impl<T: AnnotateAble> Annotated<T> {
annotations: Some(Annotations {
audience: Some(audience),
priority: None,
timestamp: None,
last_modified: None,
}),
}
}
Expand All @@ -114,7 +114,7 @@ impl<T: AnnotateAble> Annotated<T> {
raw: self.raw,
annotations: Some(Annotations {
priority: Some(priority),
timestamp: None,
last_modified: None,
audience: None,
}),
}
Expand All @@ -128,15 +128,15 @@ impl<T: AnnotateAble> Annotated<T> {
Annotated {
raw: self.raw,
annotations: Some(Annotations {
timestamp: Some(timestamp),
last_modified: Some(timestamp),
..annotations
}),
}
} else {
Annotated {
raw: self.raw,
annotations: Some(Annotations {
timestamp: Some(timestamp),
last_modified: Some(timestamp),
priority: None,
audience: None,
}),
Expand Down Expand Up @@ -211,7 +211,7 @@ pub trait AnnotateAble: sealed::Sealed {
Self: Sized,
{
self.annotate(Annotations {
timestamp: Some(timestamp),
last_modified: Some(timestamp),
..Default::default()
})
}
Expand Down
14 changes: 13 additions & 1 deletion crates/rmcp/src/model/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use super::{AnnotateAble, Annotated, resource::ResourceContents};
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct RawTextContent {
pub text: String,
/// Optional protocol-level metadata for this content block
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<super::Meta>,
}
pub type TextContent = Annotated<RawTextContent>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand All @@ -20,13 +23,17 @@ pub struct RawImageContent {
/// The base64-encoded image
pub data: String,
pub mime_type: String,
/// Optional protocol-level metadata for this content block
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<super::Meta>,
}

pub type ImageContent = Annotated<RawImageContent>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct RawEmbeddedResource {
/// Optional protocol-level metadata for this content block
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<super::Meta>,
pub resource: ResourceContents,
Expand Down Expand Up @@ -79,13 +86,17 @@ impl RawContent {
}

pub fn text<S: Into<String>>(text: S) -> Self {
RawContent::Text(RawTextContent { text: text.into() })
RawContent::Text(RawTextContent {
text: text.into(),
meta: None,
})
}

pub fn image<S: Into<String>, T: Into<String>>(data: S, mime_type: T) -> Self {
RawContent::Image(RawImageContent {
data: data.into(),
mime_type: mime_type.into(),
meta: None,
})
}

Expand Down Expand Up @@ -209,6 +220,7 @@ mod tests {
let image_content = RawImageContent {
data: "base64data".to_string(),
mime_type: "image/png".to_string(),
meta: None,
};

let json = serde_json::to_string(&image_content).unwrap();
Expand Down
23 changes: 23 additions & 0 deletions crates/rmcp/src/model/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ impl PromptMessage {
content: PromptMessageContent::Text { text: text.into() },
}
}

/// Create a new text message with meta
pub fn new_text_with_meta<S: Into<String>>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we should have ways to create each kind of prompt message with meta optionally supplied vs just this one type that has new_text and new_text_with_meta

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I think I've addressed this. LMK if me and Goose got it wrong, thanks!

role: PromptMessageRole,
text: S,
meta: Option<crate::model::Meta>,
) -> Self {
use crate::model::{AnnotateAble, RawTextContent};
let annotated = RawTextContent {
text: text.into(),
meta,
}
.no_annotation();
// Map into Content using same tagging as PromptMessageContent::Text
Self {
role,
content: PromptMessageContent::Text {
text: annotated.raw.text,
},
}
}
#[cfg(feature = "base64")]
pub fn new_image(
role: PromptMessageRole,
Expand All @@ -131,6 +152,7 @@ impl PromptMessage {
image: RawImageContent {
data: base64,
mime_type,
meta: None,
}
.optional_annotate(annotations),
},
Expand Down Expand Up @@ -184,6 +206,7 @@ mod tests {
let image_content = RawImageContent {
data: "base64data".to_string(),
mime_type: "image/png".to_string(),
meta: None,
};

let json = serde_json::to_string(&image_content).unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,19 +176,19 @@
"$ref": "#/definitions/Role"
}
},
"priority": {
"lastModified": {
"type": [
"number",
"string",
"null"
],
"format": "float"
"format": "date-time"
},
"timestamp": {
"priority": {
"type": [
"string",
"number",
"null"
],
"format": "date-time"
"format": "float"
}
}
},
Expand Down Expand Up @@ -859,6 +859,7 @@
"type": "object",
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block",
"type": [
"object",
"null"
Expand All @@ -876,6 +877,14 @@
"RawImageContent": {
"type": "object",
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block",
"type": [
"object",
"null"
],
"additionalProperties": true
},
"data": {
"description": "The base64-encoded image",
"type": "string"
Expand Down Expand Up @@ -933,6 +942,14 @@
"RawTextContent": {
"type": "object",
"properties": {
"_meta": {
"description": "Optional protocol-level metadata for this content block",
"type": [
"object",
"null"
],
"additionalProperties": true
},
"text": {
"type": "string"
}
Expand Down
Loading