From 64b0297fb62cc795569e8c71c7fd01f2290a857f Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Wed, 10 Sep 2025 20:41:08 -0400 Subject: [PATCH] feat(SEP-973): add support for icons and websiteUrl across relevant types --- crates/rmcp-macros/src/prompt.rs | 11 + crates/rmcp-macros/src/tool.rs | 11 + crates/rmcp/src/model.rs | 135 ++++++++++++ crates/rmcp/src/model/content.rs | 1 + crates/rmcp/src/model/prompt.rs | 6 +- crates/rmcp/src/model/resource.rs | 7 +- crates/rmcp/src/model/tool.rs | 6 +- crates/rmcp/tests/test_elicitation.rs | 7 +- .../client_json_rpc_message_schema.json | 52 +++++ ...lient_json_rpc_message_schema_current.json | 142 ++++++++++--- .../server_json_rpc_message_schema.json | 92 ++++++++ ...erver_json_rpc_message_schema_current.json | 199 +++++++++++++++--- examples/clients/src/sse.rs | 2 + examples/clients/src/streamable_http.rs | 2 + examples/servers/src/sampling_stdio.rs | 1 + 15 files changed, 611 insertions(+), 63 deletions(-) diff --git a/crates/rmcp-macros/src/prompt.rs b/crates/rmcp-macros/src/prompt.rs index 68a147f65..7326b6615 100644 --- a/crates/rmcp-macros/src/prompt.rs +++ b/crates/rmcp-macros/src/prompt.rs @@ -16,6 +16,8 @@ pub struct PromptAttribute { pub description: Option, /// Arguments that can be passed to the prompt pub arguments: Option, + /// Optional icons for the prompt + pub icons: Option, } pub struct ResolvedPromptAttribute { @@ -23,6 +25,7 @@ pub struct ResolvedPromptAttribute { pub title: Option, pub description: Option, pub arguments: Expr, + pub icons: Option, } impl ResolvedPromptAttribute { @@ -32,6 +35,7 @@ impl ResolvedPromptAttribute { description, arguments, title, + icons, } = self; let description = if let Some(description) = description { quote! { Some(#description.into()) } @@ -43,6 +47,11 @@ impl ResolvedPromptAttribute { } else { quote! { None } }; + let icons = if let Some(icons) = icons { + quote! { Some(#icons) } + } else { + quote! { None } + }; let tokens = quote! { pub fn #fn_ident() -> rmcp::model::Prompt { rmcp::model::Prompt { @@ -50,6 +59,7 @@ impl ResolvedPromptAttribute { description: #description, arguments: #arguments, title: #title, + icons: #icons, } } }; @@ -98,6 +108,7 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result description: description.clone(), arguments: arguments.clone(), title: attribute.title, + icons: attribute.icons, }; let prompt_attr_fn = resolved_prompt_attr.into_fn(prompt_attr_fn_ident.clone())?; diff --git a/crates/rmcp-macros/src/tool.rs b/crates/rmcp-macros/src/tool.rs index f50ba935a..68d5a70a1 100644 --- a/crates/rmcp-macros/src/tool.rs +++ b/crates/rmcp-macros/src/tool.rs @@ -75,6 +75,8 @@ pub struct ToolAttribute { pub output_schema: Option, /// Optional additional tool information. pub annotations: Option, + /// Optional icons for the tool + pub icons: Option, } pub struct ResolvedToolAttribute { @@ -84,6 +86,7 @@ pub struct ResolvedToolAttribute { pub input_schema: Expr, pub output_schema: Option, pub annotations: Expr, + pub icons: Option, } impl ResolvedToolAttribute { @@ -95,6 +98,7 @@ impl ResolvedToolAttribute { input_schema, output_schema, annotations, + icons, } = self; let description = if let Some(description) = description { quote! { Some(#description.into()) } @@ -111,6 +115,11 @@ impl ResolvedToolAttribute { } else { quote! { None } }; + let icons = if let Some(icons) = icons { + quote! { Some(#icons) } + } else { + quote! { None } + }; let tokens = quote! { pub fn #fn_ident() -> rmcp::model::Tool { rmcp::model::Tool { @@ -120,6 +129,7 @@ impl ResolvedToolAttribute { input_schema: #input_schema, output_schema: #output_schema, annotations: #annotations, + icons: #icons, } } }; @@ -240,6 +250,7 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result { output_schema: output_schema_expr, annotations: annotations_expr, title: attribute.title, + icons: attribute.icons, }; let tool_attr_fn = resolved_tool_attr.into_fn(tool_attr_fn_ident)?; // modify the the input function diff --git a/crates/rmcp/src/model.rs b/crates/rmcp/src/model.rs index e9151a3e7..840478538 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp/src/model.rs @@ -693,13 +693,41 @@ impl Default for ClientInfo { } } +/// 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: +/// - image/png - PNG images (safe, universal compatibility) +/// - image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility) +/// +/// Clients that support rendering icons SHOULD also support: +/// - image/svg+xml - SVG images (scalable but requires security precautions) +/// - image/webp - WebP images (modern, efficient format) #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct Icon { + /// A standard URI pointing to an icon resource + pub src: String, + /// Optional override if the server's MIME type is missing or generic + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// Size specification (e.g., "48x48", "any" for SVG, or "48x48 96x96") + #[serde(skip_serializing_if = "Option::is_none")] + pub sizes: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Implementation { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, pub version: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub icons: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub website_url: Option, } impl Default for Implementation { @@ -714,6 +742,8 @@ impl Implementation { name: env!("CARGO_CRATE_NAME").to_owned(), title: None, version: env!("CARGO_PKG_VERSION").to_owned(), + icons: None, + website_url: None, } } } @@ -1926,6 +1956,7 @@ mod tests { assert_eq!(capabilities.tools.unwrap().list_changed, Some(true)); assert_eq!(server_info.name, "ExampleServer"); assert_eq!(server_info.version, "1.0.0"); + assert_eq!(server_info.icons, None); assert_eq!(instructions, None); } other => panic!("Expected InitializeResult, got {other:?}"), @@ -2021,4 +2052,108 @@ mod tests { let v2 = ProtocolVersion::V_2025_03_26; assert!(v1 < v2); } + + #[test] + fn test_icon_serialization() { + let icon = Icon { + src: "https://example.com/icon.png".to_string(), + mime_type: Some("image/png".to_string()), + sizes: Some("48x48".to_string()), + }; + + 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"], "48x48"); + + // Test deserialization + let deserialized: Icon = serde_json::from_value(json).unwrap(); + assert_eq!(deserialized, icon); + } + + #[test] + fn test_icon_minimal() { + let icon = Icon { + src: "data:image/svg+xml;base64,PHN2Zy8+".to_string(), + mime_type: None, + sizes: 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()); + } + + #[test] + fn test_implementation_with_icons() { + let implementation = Implementation { + name: "test-server".to_string(), + title: Some("Test Server".to_string()), + version: "1.0.0".to_string(), + icons: Some(vec![ + Icon { + src: "https://example.com/icon.png".to_string(), + mime_type: Some("image/png".to_string()), + sizes: Some("48x48".to_string()), + }, + Icon { + src: "https://example.com/icon.svg".to_string(), + mime_type: Some("image/svg+xml".to_string()), + sizes: Some("any".to_string()), + }, + ]), + website_url: Some("https://example.com".to_string()), + }; + + let json = serde_json::to_value(&implementation).unwrap(); + assert_eq!(json["name"], "test-server"); + assert_eq!(json["websiteUrl"], "https://example.com"); + assert!(json["icons"].is_array()); + assert_eq!(json["icons"][0]["src"], "https://example.com/icon.png"); + assert_eq!(json["icons"][1]["mimeType"], "image/svg+xml"); + } + + #[test] + fn test_backward_compatibility() { + // Test that old JSON without icons still deserializes correctly + let old_json = json!({ + "name": "legacy-server", + "version": "0.9.0" + }); + + let implementation: Implementation = serde_json::from_value(old_json).unwrap(); + assert_eq!(implementation.name, "legacy-server"); + assert_eq!(implementation.version, "0.9.0"); + assert_eq!(implementation.icons, None); + assert_eq!(implementation.website_url, None); + } + + #[test] + fn test_initialize_with_icons() { + let init_result = InitializeResult { + protocol_version: ProtocolVersion::default(), + capabilities: ServerCapabilities::default(), + server_info: Implementation { + name: "icon-server".to_string(), + title: None, + version: "2.0.0".to_string(), + icons: Some(vec![Icon { + src: "https://example.com/server.png".to_string(), + mime_type: Some("image/png".to_string()), + sizes: None, + }]), + website_url: Some("https://docs.example.com".to_string()), + }, + instructions: None, + }; + + let json = serde_json::to_value(&init_result).unwrap(); + assert!(json["serverInfo"]["icons"].is_array()); + assert_eq!( + json["serverInfo"]["icons"][0]["src"], + "https://example.com/server.png" + ); + assert_eq!(json["serverInfo"]["websiteUrl"], "https://docs.example.com"); + } } diff --git a/crates/rmcp/src/model/content.rs b/crates/rmcp/src/model/content.rs index e8aa53ea2..7c60fafd9 100644 --- a/crates/rmcp/src/model/content.rs +++ b/crates/rmcp/src/model/content.rs @@ -257,6 +257,7 @@ mod tests { description: Some("A test file".to_string()), mime_type: Some("text/plain".to_string()), size: Some(100), + icons: None, }); let json = serde_json::to_string(&resource_link).unwrap(); diff --git a/crates/rmcp/src/model/prompt.rs b/crates/rmcp/src/model/prompt.rs index 15f827493..3c69758cc 100644 --- a/crates/rmcp/src/model/prompt.rs +++ b/crates/rmcp/src/model/prompt.rs @@ -2,7 +2,7 @@ use base64::engine::{Engine, general_purpose::STANDARD as BASE64_STANDARD}; use serde::{Deserialize, Serialize}; use super::{ - AnnotateAble, Annotations, RawEmbeddedResource, RawImageContent, + AnnotateAble, Annotations, Icon, RawEmbeddedResource, RawImageContent, content::{EmbeddedResource, ImageContent}, resource::ResourceContents, }; @@ -22,6 +22,9 @@ pub struct Prompt { /// Optional arguments that can be passed to customize the prompt #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option>, + /// Optional list of icons for the prompt + #[serde(skip_serializing_if = "Option::is_none")] + pub icons: Option>, } impl Prompt { @@ -40,6 +43,7 @@ impl Prompt { title: None, description: description.map(Into::into), arguments, + icons: None, } } } diff --git a/crates/rmcp/src/model/resource.rs b/crates/rmcp/src/model/resource.rs index 12b4fbc0e..a342ad4e7 100644 --- a/crates/rmcp/src/model/resource.rs +++ b/crates/rmcp/src/model/resource.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use super::{Annotated, Meta}; +use super::{Annotated, Icon, Meta}; /// Represents a resource in the extension with metadata #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -26,6 +26,9 @@ pub struct RawResource { /// This can be used by Hosts to display file sizes and estimate context window us #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, + /// Optional list of icons for the resource + #[serde(skip_serializing_if = "Option::is_none")] + pub icons: Option>, } pub type Resource = Annotated; @@ -91,6 +94,7 @@ impl RawResource { description: None, mime_type: None, size: None, + icons: None, } } } @@ -110,6 +114,7 @@ mod tests { description: Some("Test resource".to_string()), mime_type: Some("text/plain".to_string()), size: Some(100), + icons: None, }; let json = serde_json::to_string(&resource).unwrap(); diff --git a/crates/rmcp/src/model/tool.rs b/crates/rmcp/src/model/tool.rs index 225534fbc..282d8aa3c 100644 --- a/crates/rmcp/src/model/tool.rs +++ b/crates/rmcp/src/model/tool.rs @@ -6,7 +6,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; -use super::JsonObject; +use super::{Icon, JsonObject}; /// A tool that can be used by a model. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -29,6 +29,9 @@ pub struct Tool { #[serde(skip_serializing_if = "Option::is_none")] /// Optional additional tool information. pub annotations: Option, + /// Optional list of icons for the tool + #[serde(skip_serializing_if = "Option::is_none")] + pub icons: Option>, } /// Additional properties describing a Tool to clients. @@ -146,6 +149,7 @@ impl Tool { input_schema: input_schema.into(), output_schema: None, annotations: None, + icons: None, } } diff --git a/crates/rmcp/tests/test_elicitation.rs b/crates/rmcp/tests/test_elicitation.rs index 3b3ea01ac..4f8abbf80 100644 --- a/crates/rmcp/tests/test_elicitation.rs +++ b/crates/rmcp/tests/test_elicitation.rs @@ -893,6 +893,8 @@ async fn test_initialize_request_with_elicitation() { name: "test-client".to_string(), version: "1.0.0".to_string(), title: None, + website_url: None, + icons: None, }, }; @@ -935,6 +937,8 @@ async fn test_capability_checking_logic() { name: "test-client".to_string(), version: "1.0.0".to_string(), title: None, + website_url: None, + icons: None, }, }; @@ -953,9 +957,10 @@ async fn test_capability_checking_logic() { name: "test-client".to_string(), version: "1.0.0".to_string(), title: None, + website_url: None, + icons: None, }, }; - let supports_elicitation = client_without_capability.capabilities.elicitation.is_some(); assert!(!supports_elicitation); } 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 6606ca8f2..597b86d48 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 @@ -487,9 +487,45 @@ "name" ] }, + "Icon": { + "description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)", + "type": "object", + "properties": { + "mimeType": { + "description": "Optional override if the server's MIME type is missing or generic", + "type": [ + "string", + "null" + ] + }, + "sizes": { + "description": "Size specification (e.g., \"48x48\", \"any\" for SVG, or \"48x48 96x96\")", + "type": [ + "string", + "null" + ] + }, + "src": { + "description": "A standard URI pointing to an icon resource", + "type": "string" + } + }, + "required": [ + "src" + ] + }, "Implementation": { "type": "object", "properties": { + "icons": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "name": { "type": "string" }, @@ -501,6 +537,12 @@ }, "version": { "type": "string" + }, + "websiteUrl": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -938,6 +980,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ 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 bc1dd40f3..597b86d48 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 @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "JsonRpcMessage", - "description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, batch operations, and errors.\nIt serves as the top-level message container for MCP communication.", + "description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, and errors.\nIt serves as the top-level message container for MCP communication.", "anyOf": [ { "description": "A single request expecting a response", @@ -113,7 +113,7 @@ }, "allOf": [ { - "$ref": "#/definitions/Annotated2" + "$ref": "#/definitions/RawAudioContent" } ], "required": [ @@ -139,31 +139,6 @@ } ] }, - "Annotated2": { - "type": "object", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType" - ] - }, "Annotations": { "type": "object", "properties": { @@ -327,6 +302,17 @@ "argument": { "$ref": "#/definitions/ArgumentInfo" }, + "context": { + "description": "Optional context containing previously resolved argument values", + "anyOf": [ + { + "$ref": "#/definitions/CompletionContext" + }, + { + "type": "null" + } + ] + }, "ref": { "$ref": "#/definitions/Reference" } @@ -336,6 +322,22 @@ "argument" ] }, + "CompletionContext": { + "description": "Context for completion requests providing previously resolved arguments.\n\nThis enables context-aware completion where subsequent argument completions\ncan take into account the values of previously resolved arguments.", + "type": "object", + "properties": { + "arguments": { + "description": "Previously resolved argument values that can inform completion suggestions", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + } + } + }, "CreateElicitationResult": { "description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.", "type": "object", @@ -485,14 +487,62 @@ "name" ] }, + "Icon": { + "description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)", + "type": "object", + "properties": { + "mimeType": { + "description": "Optional override if the server's MIME type is missing or generic", + "type": [ + "string", + "null" + ] + }, + "sizes": { + "description": "Size specification (e.g., \"48x48\", \"any\" for SVG, or \"48x48 96x96\")", + "type": [ + "string", + "null" + ] + }, + "src": { + "description": "A standard URI pointing to an icon resource", + "type": "string" + } + }, + "required": [ + "src" + ] + }, "Implementation": { "type": "object", "properties": { + "icons": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "name": { "type": "string" }, + "title": { + "type": [ + "string", + "null" + ] + }, "version": { "type": "string" + }, + "websiteUrl": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -845,6 +895,12 @@ "properties": { "name": { "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -855,6 +911,21 @@ "description": "Represents the MCP protocol version used for communication.\n\nThis ensures compatibility between clients and servers by specifying\nwhich version of the Model Context Protocol is being used.", "type": "string" }, + "RawAudioContent": { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType" + ] + }, "RawEmbeddedResource": { "type": "object", "properties": { @@ -909,6 +980,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ @@ -929,6 +1010,13 @@ "format": "uint32", "minimum": 0 }, + "title": { + "description": "Human-readable title of the resource", + "type": [ + "string", + "null" + ] + }, "uri": { "description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")", "type": "string" 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 b81fb9254..89ce598db 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 @@ -189,6 +189,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ @@ -608,9 +618,45 @@ "messages" ] }, + "Icon": { + "description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)", + "type": "object", + "properties": { + "mimeType": { + "description": "Optional override if the server's MIME type is missing or generic", + "type": [ + "string", + "null" + ] + }, + "sizes": { + "description": "Size specification (e.g., \"48x48\", \"any\" for SVG, or \"48x48 96x96\")", + "type": [ + "string", + "null" + ] + }, + "src": { + "description": "A standard URI pointing to an icon resource", + "type": "string" + } + }, + "required": [ + "src" + ] + }, "Implementation": { "type": "object", "properties": { + "icons": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "name": { "type": "string" }, @@ -622,6 +668,12 @@ }, "version": { "type": "string" + }, + "websiteUrl": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -1136,6 +1188,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the prompt", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "name": { "description": "The name of the prompt", "type": "string" @@ -1314,6 +1376,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ @@ -1450,6 +1522,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ @@ -1837,6 +1919,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the tool", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "inputSchema": { "description": "A JSON Schema object defining the expected parameters for the tool", "type": "object", 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 0b9812e28..89ce598db 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 @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "JsonRpcMessage", - "description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, batch operations, and errors.\nIt serves as the top-level message container for MCP communication.", + "description": "Represents any JSON-RPC message that can be sent or received.\n\nThis enum covers all possible message types in the JSON-RPC protocol:\nindividual requests/responses, notifications, and errors.\nIt serves as the top-level message container for MCP communication.", "anyOf": [ { "description": "A single request expecting a response", @@ -113,7 +113,7 @@ }, "allOf": [ { - "$ref": "#/definitions/Annotated2" + "$ref": "#/definitions/RawAudioContent" } ], "required": [ @@ -140,31 +140,6 @@ ] }, "Annotated2": { - "type": "object", - "properties": { - "annotations": { - "anyOf": [ - { - "$ref": "#/definitions/Annotations" - }, - { - "type": "null" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - } - }, - "required": [ - "data", - "mimeType" - ] - }, - "Annotated3": { "type": "object", "properties": { "_meta": { @@ -193,7 +168,7 @@ "resource" ] }, - "Annotated4": { + "Annotated3": { "description": "Represents a resource in the extension with metadata", "type": "object", "properties": { @@ -214,6 +189,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ @@ -234,6 +219,13 @@ "format": "uint32", "minimum": 0 }, + "title": { + "description": "Human-readable title of the resource", + "type": [ + "string", + "null" + ] + }, "uri": { "description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")", "type": "string" @@ -244,7 +236,7 @@ "name" ] }, - "Annotated5": { + "Annotated4": { "type": "object", "properties": { "annotations": { @@ -272,6 +264,12 @@ "name": { "type": "string" }, + "title": { + "type": [ + "string", + "null" + ] + }, "uriTemplate": { "type": "string" } @@ -620,14 +618,62 @@ "messages" ] }, + "Icon": { + "description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)", + "type": "object", + "properties": { + "mimeType": { + "description": "Optional override if the server's MIME type is missing or generic", + "type": [ + "string", + "null" + ] + }, + "sizes": { + "description": "Size specification (e.g., \"48x48\", \"any\" for SVG, or \"48x48 96x96\")", + "type": [ + "string", + "null" + ] + }, + "src": { + "description": "A standard URI pointing to an icon resource", + "type": "string" + } + }, + "required": [ + "src" + ] + }, "Implementation": { "type": "object", "properties": { + "icons": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "name": { "type": "string" }, + "title": { + "type": [ + "string", + "null" + ] + }, "version": { "type": "string" + }, + "websiteUrl": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -815,7 +861,7 @@ "resourceTemplates": { "type": "array", "items": { - "$ref": "#/definitions/Annotated5" + "$ref": "#/definitions/Annotated4" } } }, @@ -835,7 +881,7 @@ "resources": { "type": "array", "items": { - "$ref": "#/definitions/Annotated4" + "$ref": "#/definitions/Annotated3" } } }, @@ -1142,9 +1188,25 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the prompt", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "name": { "description": "The name of the prompt", "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -1172,6 +1234,13 @@ "boolean", "null" ] + }, + "title": { + "description": "A human-readable title for the argument", + "type": [ + "string", + "null" + ] } }, "required": [ @@ -1274,7 +1343,7 @@ "type": "object", "properties": { "resource": { - "$ref": "#/definitions/Annotated3" + "$ref": "#/definitions/Annotated2" }, "type": { "type": "string", @@ -1307,6 +1376,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ @@ -1327,6 +1406,13 @@ "format": "uint32", "minimum": 0 }, + "title": { + "description": "Human-readable title of the resource", + "type": [ + "string", + "null" + ] + }, "type": { "type": "string", "const": "resource_link" @@ -1367,6 +1453,21 @@ "description": "Represents the MCP protocol version used for communication.\n\nThis ensures compatibility between clients and servers by specifying\nwhich version of the Model Context Protocol is being used.", "type": "string" }, + "RawAudioContent": { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + }, + "required": [ + "data", + "mimeType" + ] + }, "RawEmbeddedResource": { "type": "object", "properties": { @@ -1421,6 +1522,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the resource", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "mimeType": { "description": "MIME type of the resource content (\"text\" or \"blob\")", "type": [ @@ -1441,6 +1552,13 @@ "format": "uint32", "minimum": 0 }, + "title": { + "description": "Human-readable title of the resource", + "type": [ + "string", + "null" + ] + }, "uri": { "description": "URI representing the resource location (e.g., \"file:///path/to/file\" or \"str:///content\")", "type": "string" @@ -1801,6 +1919,16 @@ "null" ] }, + "icons": { + "description": "Optional list of icons for the tool", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Icon" + } + }, "inputSchema": { "description": "A JSON Schema object defining the expected parameters for the tool", "type": "object", @@ -1817,6 +1945,13 @@ "null" ], "additionalProperties": true + }, + "title": { + "description": "A human-readable title for the tool", + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/examples/clients/src/sse.rs b/examples/clients/src/sse.rs index aaff4f48f..11e5f3c5c 100644 --- a/examples/clients/src/sse.rs +++ b/examples/clients/src/sse.rs @@ -24,6 +24,8 @@ async fn main() -> Result<()> { name: "test sse client".to_string(), title: None, version: "0.0.1".to_string(), + website_url: None, + icons: None, }, }; let client = client_info.serve(transport).await.inspect_err(|e| { diff --git a/examples/clients/src/streamable_http.rs b/examples/clients/src/streamable_http.rs index 1fef3c6ea..2f1f15985 100644 --- a/examples/clients/src/streamable_http.rs +++ b/examples/clients/src/streamable_http.rs @@ -24,6 +24,8 @@ async fn main() -> Result<()> { name: "test sse client".to_string(), title: None, version: "0.0.1".to_string(), + website_url: None, + icons: None, }, }; let client = client_info.serve(transport).await.inspect_err(|e| { diff --git a/examples/servers/src/sampling_stdio.rs b/examples/servers/src/sampling_stdio.rs index 3afec818c..4daa261c8 100644 --- a/examples/servers/src/sampling_stdio.rs +++ b/examples/servers/src/sampling_stdio.rs @@ -122,6 +122,7 @@ impl ServerHandler for SamplingDemoServer { ), output_schema: None, annotations: None, + icons: None, }], next_cursor: None, })