From 0189f5d199465486f392386284e6e5c1e2725905 Mon Sep 17 00:00:00 2001 From: Guy Lichtman <1395797+glicht@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:23:01 +0200 Subject: [PATCH 1/4] feat: add theme field to Icon --- crates/rmcp/src/model.rs | 29 +++++++++++++++++++ crates/rmcp/src/model/resource.rs | 3 ++ .../client_json_rpc_message_schema.json | 19 ++++++++++++ ...lient_json_rpc_message_schema_current.json | 19 ++++++++++++ .../server_json_rpc_message_schema.json | 19 ++++++++++++ ...erver_json_rpc_message_schema_current.json | 19 ++++++++++++ 6 files changed, 108 insertions(+) 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": { From a9f3d28dbd7e57847ef4ce5d4c9963c17fc33940 Mon Sep 17 00:00:00 2001 From: Guy Lichtman <1395797+glicht@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:47:24 +0200 Subject: [PATCH 2/4] fix: update IconThem crates/rmcp/src/model.rs (non_exhaustive) Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com> --- crates/rmcp/src/model.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rmcp/src/model.rs b/crates/rmcp/src/model.rs index 0b7c314b1..cc3819728 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp/src/model.rs @@ -893,6 +893,7 @@ impl Default for ClientInfo { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)] #[serde(rename_all = "lowercase")] //match spec #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[non_exhaustive] pub enum IconTheme { Light, Dark, From 74deead37ed4dc05f7bbb327ef0a4fa090a13706 Mon Sep 17 00:00:00 2001 From: Guy Lichtman <1395797+glicht@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:47:49 +0200 Subject: [PATCH 3/4] fix: update IconThem crates/rmcp/src/model.rs (eq, hash) Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com> --- crates/rmcp/src/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rmcp/src/model.rs b/crates/rmcp/src/model.rs index cc3819728..cf622e1a2 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp/src/model.rs @@ -890,7 +890,7 @@ impl Default for ClientInfo { } /// Icon themes supported by the MCP specification -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)] #[serde(rename_all = "lowercase")] //match spec #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] From bba6fd97661c08ba3d9cd5b2e1afed0b68689d3a Mon Sep 17 00:00:00 2001 From: Guy Lichtman <1395797+glicht@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:01:34 +0200 Subject: [PATCH 4/4] fix: update docs with full descriptions of theme from mcp spec --- crates/rmcp/src/model.rs | 3 +++ .../client_json_rpc_message_schema.json | 17 ++++++++++++----- .../client_json_rpc_message_schema_current.json | 17 ++++++++++++----- .../server_json_rpc_message_schema.json | 17 ++++++++++++----- .../server_json_rpc_message_schema_current.json | 17 ++++++++++++----- 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/crates/rmcp/src/model.rs b/crates/rmcp/src/model.rs index cf622e1a2..74ebbad28 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp/src/model.rs @@ -895,7 +895,9 @@ impl Default for ClientInfo { #[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, } @@ -922,6 +924,7 @@ pub struct Icon { #[serde(skip_serializing_if = "Option::is_none")] pub sizes: Option>, /// 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, } 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 f8cac1fcd..397fbe8bc 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 @@ -722,7 +722,7 @@ "type": "string" }, "theme": { - "description": "Optional specifier for the theme this icon is designed for", + "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" @@ -739,10 +739,17 @@ }, "IconTheme": { "description": "Icon themes supported by the MCP specification", - "type": "string", - "enum": [ - "light", - "dark" + "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": { 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 f8cac1fcd..397fbe8bc 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 @@ -722,7 +722,7 @@ "type": "string" }, "theme": { - "description": "Optional specifier for the theme this icon is designed for", + "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" @@ -739,10 +739,17 @@ }, "IconTheme": { "description": "Icon themes supported by the MCP specification", - "type": "string", - "enum": [ - "light", - "dark" + "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": { 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 b5a134c32..db21c2ba6 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 @@ -1092,7 +1092,7 @@ "type": "string" }, "theme": { - "description": "Optional specifier for the theme this icon is designed for", + "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" @@ -1109,10 +1109,17 @@ }, "IconTheme": { "description": "Icon themes supported by the MCP specification", - "type": "string", - "enum": [ - "light", - "dark" + "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": { 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 b5a134c32..db21c2ba6 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 @@ -1092,7 +1092,7 @@ "type": "string" }, "theme": { - "description": "Optional specifier for the theme this icon is designed for", + "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" @@ -1109,10 +1109,17 @@ }, "IconTheme": { "description": "Icon themes supported by the MCP specification", - "type": "string", - "enum": [ - "light", - "dark" + "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": {