diff --git a/crates/rmcp/src/model.rs b/crates/rmcp/src/model.rs index fa47ea788..0b7c314b1 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp/src/model.rs @@ -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: @@ -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>, + /// Optional specifier for the theme this icon is designed for + #[serde(skip_serializing_if = "Option::is_none")] + pub theme: Option, } impl Icon { @@ -920,6 +932,7 @@ impl Icon { src: src.into(), mime_type: None, sizes: None, + theme: None, } } @@ -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)] @@ -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(); @@ -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] @@ -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()), @@ -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] @@ -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()), }, @@ -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"); } diff --git a/crates/rmcp/src/model/resource.rs b/crates/rmcp/src/model/resource.rs index 8a25e25ba..2e004d679 100644 --- a/crates/rmcp/src/model/resource.rs +++ b/crates/rmcp/src/model/resource.rs @@ -214,6 +214,7 @@ mod tests { use serde_json; use super::*; + use crate::model::IconTheme; #[test] fn test_resource_serialization() { @@ -265,6 +266,7 @@ 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), }]), }; @@ -272,6 +274,7 @@ mod tests { 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] diff --git a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json index 940f03f1b..f8cac1fcd 100644 --- a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json +++ b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json @@ -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": { diff --git a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json index 940f03f1b..f8cac1fcd 100644 --- a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json +++ b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json @@ -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": { diff --git a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json index bd8f744b0..b5a134c32 100644 --- a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json +++ b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json @@ -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": { diff --git a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json index bd8f744b0..b5a134c32 100644 --- a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json +++ b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json @@ -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": {