Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions crates/rmcp/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,15 @@ impl Default for ClientInfo {
}
}

/// Icon themes supported by the MCP specification
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
#[serde(rename_all = "lowercase")] //match spec
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum IconTheme {
Light,
Dark,
}

/// A URL pointing to an icon resource or a base64-encoded data URI.
///
/// Clients that support rendering icons MUST support at least the following MIME types:
Expand All @@ -911,6 +920,9 @@ pub struct Icon {
/// Size specification, each string should be in WxH format (e.g., `\"48x48\"`, `\"96x96\"`) or `\"any\"` for scalable formats like SVG
#[serde(skip_serializing_if = "Option::is_none")]
pub sizes: Option<Vec<String>>,
/// Optional specifier for the theme this icon is designed for
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<IconTheme>,
}

impl Icon {
Expand All @@ -920,6 +932,7 @@ impl Icon {
src: src.into(),
mime_type: None,
sizes: None,
theme: None,
}
}

Expand All @@ -934,6 +947,12 @@ impl Icon {
self.sizes = Some(sizes);
self
}

/// Set the theme.
pub fn with_theme(mut self, theme: IconTheme) -> Self {
self.theme = Some(theme);
self
}
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
Expand Down Expand Up @@ -3642,12 +3661,14 @@ mod tests {
src: "https://example.com/icon.png".to_string(),
mime_type: Some("image/png".to_string()),
sizes: Some(vec!["48x48".to_string()]),
theme: Some(IconTheme::Light),
};

let json = serde_json::to_value(&icon).unwrap();
assert_eq!(json["src"], "https://example.com/icon.png");
assert_eq!(json["mimeType"], "image/png");
assert_eq!(json["sizes"][0], "48x48");
assert_eq!(json["theme"], "light");

// Test deserialization
let deserialized: Icon = serde_json::from_value(json).unwrap();
Expand All @@ -3660,12 +3681,14 @@ mod tests {
src: "data:image/svg+xml;base64,PHN2Zy8+".to_string(),
mime_type: None,
sizes: None,
theme: None,
};

let json = serde_json::to_value(&icon).unwrap();
assert_eq!(json["src"], "data:image/svg+xml;base64,PHN2Zy8+");
assert!(json.get("mimeType").is_none());
assert!(json.get("sizes").is_none());
assert!(json.get("theme").is_none());
}

#[test]
Expand All @@ -3680,11 +3703,13 @@ mod tests {
src: "https://example.com/icon.png".to_string(),
mime_type: Some("image/png".to_string()),
sizes: Some(vec!["48x48".to_string()]),
theme: Some(IconTheme::Dark),
},
Icon {
src: "https://example.com/icon.svg".to_string(),
mime_type: Some("image/svg+xml".to_string()),
sizes: Some(vec!["any".to_string()]),
theme: Some(IconTheme::Light),
},
]),
website_url: Some("https://example.com".to_string()),
Expand All @@ -3699,6 +3724,8 @@ mod tests {
assert_eq!(json["icons"][0]["sizes"][0], "48x48");
assert_eq!(json["icons"][1]["mimeType"], "image/svg+xml");
assert_eq!(json["icons"][1]["sizes"][0], "any");
assert_eq!(json["icons"][0]["theme"], "dark");
assert_eq!(json["icons"][1]["theme"], "light");
}

#[test]
Expand Down Expand Up @@ -3731,6 +3758,7 @@ mod tests {
src: "https://example.com/server.png".to_string(),
mime_type: Some("image/png".to_string()),
sizes: Some(vec!["48x48".to_string()]),
theme: Some(IconTheme::Light),
}]),
website_url: Some("https://docs.example.com".to_string()),
},
Expand All @@ -3744,6 +3772,7 @@ mod tests {
"https://example.com/server.png"
);
assert_eq!(json["serverInfo"]["icons"][0]["sizes"][0], "48x48");
assert_eq!(json["serverInfo"]["icons"][0]["theme"], "light");
assert_eq!(json["serverInfo"]["websiteUrl"], "https://docs.example.com");
}

Expand Down
3 changes: 3 additions & 0 deletions crates/rmcp/src/model/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ mod tests {
use serde_json;

use super::*;
use crate::model::IconTheme;

#[test]
fn test_resource_serialization() {
Expand Down Expand Up @@ -265,13 +266,15 @@ mod tests {
src: "https://example.com/icon.png".to_string(),
mime_type: Some("image/png".to_string()),
sizes: Some(vec!["48x48".to_string()]),
theme: Some(IconTheme::Light),
}]),
};

let json = serde_json::to_value(&resource_template).unwrap();
assert!(json["icons"].is_array());
assert_eq!(json["icons"][0]["src"], "https://example.com/icon.png");
assert_eq!(json["icons"][0]["sizes"][0], "48x48");
assert_eq!(json["icons"][0]["theme"], "light");
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -720,12 +720,31 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"type": "string",
"enum": [
"light",
"dark"
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -720,12 +720,31 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"type": "string",
"enum": [
"light",
"dark"
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1090,12 +1090,31 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"type": "string",
"enum": [
"light",
"dark"
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1090,12 +1090,31 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"type": "string",
"enum": [
"light",
"dark"
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Loading