feat: add "theme" to Icon (#766)
* feat: add theme field to Icon * fix: update IconThem crates/rmcp/src/model.rs (non_exhaustive) Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com> * fix: update IconThem crates/rmcp/src/model.rs (eq, hash) Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com> * fix: update docs with full descriptions of theme from mcp spec --------- Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
This commit is contained in:
parent
6a3b32d3ab
commit
0b36a84f05
6 changed files with 140 additions and 0 deletions
|
|
@ -907,6 +907,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.
|
/// 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:
|
/// Clients that support rendering icons MUST support at least the following MIME types:
|
||||||
|
|
@ -929,6 +941,10 @@ pub struct Icon {
|
||||||
/// Size specification, each string should be in WxH format (e.g., `\"48x48\"`, `\"96x96\"`) or `\"any\"` for scalable formats like SVG
|
/// 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")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub sizes: Option<Vec<String>>,
|
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 {
|
impl Icon {
|
||||||
|
|
@ -938,6 +954,7 @@ impl Icon {
|
||||||
src: src.into(),
|
src: src.into(),
|
||||||
mime_type: None,
|
mime_type: None,
|
||||||
sizes: None,
|
sizes: None,
|
||||||
|
theme: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -952,6 +969,12 @@ impl Icon {
|
||||||
self.sizes = Some(sizes);
|
self.sizes = Some(sizes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the theme.
|
||||||
|
pub fn with_theme(mut self, theme: IconTheme) -> Self {
|
||||||
|
self.theme = Some(theme);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
|
@ -3725,12 +3748,14 @@ mod tests {
|
||||||
src: "https://example.com/icon.png".to_string(),
|
src: "https://example.com/icon.png".to_string(),
|
||||||
mime_type: Some("image/png".to_string()),
|
mime_type: Some("image/png".to_string()),
|
||||||
sizes: Some(vec!["48x48".to_string()]),
|
sizes: Some(vec!["48x48".to_string()]),
|
||||||
|
theme: Some(IconTheme::Light),
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&icon).unwrap();
|
let json = serde_json::to_value(&icon).unwrap();
|
||||||
assert_eq!(json["src"], "https://example.com/icon.png");
|
assert_eq!(json["src"], "https://example.com/icon.png");
|
||||||
assert_eq!(json["mimeType"], "image/png");
|
assert_eq!(json["mimeType"], "image/png");
|
||||||
assert_eq!(json["sizes"][0], "48x48");
|
assert_eq!(json["sizes"][0], "48x48");
|
||||||
|
assert_eq!(json["theme"], "light");
|
||||||
|
|
||||||
// Test deserialization
|
// Test deserialization
|
||||||
let deserialized: Icon = serde_json::from_value(json).unwrap();
|
let deserialized: Icon = serde_json::from_value(json).unwrap();
|
||||||
|
|
@ -3743,12 +3768,14 @@ mod tests {
|
||||||
src: "data:image/svg+xml;base64,PHN2Zy8+".to_string(),
|
src: "data:image/svg+xml;base64,PHN2Zy8+".to_string(),
|
||||||
mime_type: None,
|
mime_type: None,
|
||||||
sizes: None,
|
sizes: None,
|
||||||
|
theme: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_value(&icon).unwrap();
|
let json = serde_json::to_value(&icon).unwrap();
|
||||||
assert_eq!(json["src"], "data:image/svg+xml;base64,PHN2Zy8+");
|
assert_eq!(json["src"], "data:image/svg+xml;base64,PHN2Zy8+");
|
||||||
assert!(json.get("mimeType").is_none());
|
assert!(json.get("mimeType").is_none());
|
||||||
assert!(json.get("sizes").is_none());
|
assert!(json.get("sizes").is_none());
|
||||||
|
assert!(json.get("theme").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -3763,11 +3790,13 @@ mod tests {
|
||||||
src: "https://example.com/icon.png".to_string(),
|
src: "https://example.com/icon.png".to_string(),
|
||||||
mime_type: Some("image/png".to_string()),
|
mime_type: Some("image/png".to_string()),
|
||||||
sizes: Some(vec!["48x48".to_string()]),
|
sizes: Some(vec!["48x48".to_string()]),
|
||||||
|
theme: Some(IconTheme::Dark),
|
||||||
},
|
},
|
||||||
Icon {
|
Icon {
|
||||||
src: "https://example.com/icon.svg".to_string(),
|
src: "https://example.com/icon.svg".to_string(),
|
||||||
mime_type: Some("image/svg+xml".to_string()),
|
mime_type: Some("image/svg+xml".to_string()),
|
||||||
sizes: Some(vec!["any".to_string()]),
|
sizes: Some(vec!["any".to_string()]),
|
||||||
|
theme: Some(IconTheme::Light),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
website_url: Some("https://example.com".to_string()),
|
website_url: Some("https://example.com".to_string()),
|
||||||
|
|
@ -3782,6 +3811,8 @@ mod tests {
|
||||||
assert_eq!(json["icons"][0]["sizes"][0], "48x48");
|
assert_eq!(json["icons"][0]["sizes"][0], "48x48");
|
||||||
assert_eq!(json["icons"][1]["mimeType"], "image/svg+xml");
|
assert_eq!(json["icons"][1]["mimeType"], "image/svg+xml");
|
||||||
assert_eq!(json["icons"][1]["sizes"][0], "any");
|
assert_eq!(json["icons"][1]["sizes"][0], "any");
|
||||||
|
assert_eq!(json["icons"][0]["theme"], "dark");
|
||||||
|
assert_eq!(json["icons"][1]["theme"], "light");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -3814,6 +3845,7 @@ mod tests {
|
||||||
src: "https://example.com/server.png".to_string(),
|
src: "https://example.com/server.png".to_string(),
|
||||||
mime_type: Some("image/png".to_string()),
|
mime_type: Some("image/png".to_string()),
|
||||||
sizes: Some(vec!["48x48".to_string()]),
|
sizes: Some(vec!["48x48".to_string()]),
|
||||||
|
theme: Some(IconTheme::Light),
|
||||||
}]),
|
}]),
|
||||||
website_url: Some("https://docs.example.com".to_string()),
|
website_url: Some("https://docs.example.com".to_string()),
|
||||||
},
|
},
|
||||||
|
|
@ -3827,6 +3859,7 @@ mod tests {
|
||||||
"https://example.com/server.png"
|
"https://example.com/server.png"
|
||||||
);
|
);
|
||||||
assert_eq!(json["serverInfo"]["icons"][0]["sizes"][0], "48x48");
|
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");
|
assert_eq!(json["serverInfo"]["websiteUrl"], "https://docs.example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ mod tests {
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::model::IconTheme;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resource_serialization() {
|
fn test_resource_serialization() {
|
||||||
|
|
@ -268,6 +269,7 @@ mod tests {
|
||||||
src: "https://example.com/icon.png".to_string(),
|
src: "https://example.com/icon.png".to_string(),
|
||||||
mime_type: Some("image/png".to_string()),
|
mime_type: Some("image/png".to_string()),
|
||||||
sizes: Some(vec!["48x48".to_string()]),
|
sizes: Some(vec!["48x48".to_string()]),
|
||||||
|
theme: Some(IconTheme::Light),
|
||||||
}]),
|
}]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -275,6 +277,7 @@ mod tests {
|
||||||
assert!(json["icons"].is_array());
|
assert!(json["icons"].is_array());
|
||||||
assert_eq!(json["icons"][0]["src"], "https://example.com/icon.png");
|
assert_eq!(json["icons"][0]["src"], "https://example.com/icon.png");
|
||||||
assert_eq!(json["icons"][0]["sizes"][0], "48x48");
|
assert_eq!(json["icons"][0]["sizes"][0], "48x48");
|
||||||
|
assert_eq!(json["icons"][0]["theme"], "light");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -720,12 +720,38 @@
|
||||||
"src": {
|
"src": {
|
||||||
"description": "A standard URI pointing to an icon resource",
|
"description": "A standard URI pointing to an icon resource",
|
||||||
"type": "string"
|
"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": [
|
"required": [
|
||||||
"src"
|
"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": {
|
"Implementation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -720,12 +720,38 @@
|
||||||
"src": {
|
"src": {
|
||||||
"description": "A standard URI pointing to an icon resource",
|
"description": "A standard URI pointing to an icon resource",
|
||||||
"type": "string"
|
"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": [
|
"required": [
|
||||||
"src"
|
"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": {
|
"Implementation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -1090,12 +1090,38 @@
|
||||||
"src": {
|
"src": {
|
||||||
"description": "A standard URI pointing to an icon resource",
|
"description": "A standard URI pointing to an icon resource",
|
||||||
"type": "string"
|
"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": [
|
"required": [
|
||||||
"src"
|
"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": {
|
"Implementation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -1092,12 +1092,38 @@
|
||||||
"src": {
|
"src": {
|
||||||
"description": "A standard URI pointing to an icon resource",
|
"description": "A standard URI pointing to an icon resource",
|
||||||
"type": "string"
|
"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": [
|
"required": [
|
||||||
"src"
|
"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": {
|
"Implementation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue