Skip to content
Merged
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
33 changes: 33 additions & 0 deletions crates/rmcp/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,18 @@ impl Default for ClientInfo {
}
}

/// Icon themes supported by the MCP specification
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)]
#[serde(rename_all = "lowercase")] //match spec
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum IconTheme {
/// Indicates the icon is designed to be used with a light background
Light,
/// Indicates the icon is designed to be used with a dark background
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 +923,10 @@ 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
/// If not provided, the client should assume the icon can be used with any theme.
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<IconTheme>,
}

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

Expand All @@ -934,6 +951,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 +3665,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 +3685,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 +3707,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 +3728,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 +3762,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 +3776,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,38 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for\nIf not provided, the client should assume the icon can be used with any theme.",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"oneOf": [
{
"description": "Indicates the icon is designed to be used with a light background",
"type": "string",
"const": "light"
},
{
"description": "Indicates the icon is designed to be used with a dark background",
"type": "string",
"const": "dark"
}
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -720,12 +720,38 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for\nIf not provided, the client should assume the icon can be used with any theme.",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"oneOf": [
{
"description": "Indicates the icon is designed to be used with a light background",
"type": "string",
"const": "light"
},
{
"description": "Indicates the icon is designed to be used with a dark background",
"type": "string",
"const": "dark"
}
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1090,12 +1090,38 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for\nIf not provided, the client should assume the icon can be used with any theme.",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"oneOf": [
{
"description": "Indicates the icon is designed to be used with a light background",
"type": "string",
"const": "light"
},
{
"description": "Indicates the icon is designed to be used with a dark background",
"type": "string",
"const": "dark"
}
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1090,12 +1090,38 @@
"src": {
"description": "A standard URI pointing to an icon resource",
"type": "string"
},
"theme": {
"description": "Optional specifier for the theme this icon is designed for\nIf not provided, the client should assume the icon can be used with any theme.",
"anyOf": [
{
"$ref": "#/definitions/IconTheme"
},
{
"type": "null"
}
]
}
},
"required": [
"src"
]
},
"IconTheme": {
"description": "Icon themes supported by the MCP specification",
"oneOf": [
{
"description": "Indicates the icon is designed to be used with a light background",
"type": "string",
"const": "light"
},
{
"description": "Indicates the icon is designed to be used with a dark background",
"type": "string",
"const": "dark"
}
]
},
"Implementation": {
"type": "object",
"properties": {
Expand Down
Loading