diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a415fb..e213730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,7 +143,7 @@ ### Bug Fixes -* custom result deserialization conflic with rust_mcp_schema::Result ([#44](https://github.com/rust-mcp-stack/rust-mcp-schema/issues/44)) ([f141060](https://github.com/rust-mcp-stack/rust-mcp-schema/commit/f14106047ee6fdc499f0915ea2029954cf06d634)) +* custom result deserialization conflict with rust_mcp_schema::Result ([#44](https://github.com/rust-mcp-stack/rust-mcp-schema/issues/44)) ([f141060](https://github.com/rust-mcp-stack/rust-mcp-schema/commit/f14106047ee6fdc499f0915ea2029954cf06d634)) ## [0.1.9](https://github.com/rust-mcp-stack/rust-mcp-schema/compare/v0.1.8...v0.1.9) (2025-03-02) diff --git a/README.md b/README.md index 8731198..9681d0f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Multiple schema versions may be enabled concurrently if needed. Non-default vers - rust_mcp_schema::mcp_2024_11_05 - rust_mcp_schema::mcp_draft" -Example: enable `2024_11_05` version of the shema: +Example: enable `2024_11_05` version of the schema: @@ -95,7 +95,7 @@ Example: enable `2024_11_05` version of the shema: rust-mcp-schema = { version: 0.7.4 , default-features = false, features=["2024_11_05"] } ``` -Example: enable `draft`` version of the shema (2024_11_05) : +Example: enable `draft`` version of the schema (2024_11_05) : ```toml #Cargo.toml diff --git a/src/generated_schema.rs b/src/generated_schema.rs index faf31a2..be88bf1 100644 --- a/src/generated_schema.rs +++ b/src/generated_schema.rs @@ -4,6 +4,7 @@ macro_rules! define_schema_version { $mod_name:ident, $schema_path:literal, $utils_path:literal, + $validators_path:literal, $schema_mod:ident, $utils_mod:ident ) => { @@ -15,6 +16,9 @@ macro_rules! define_schema_version { #[path = $utils_path] mod $utils_mod; + #[path = $validators_path] + mod validators; + #[cfg(feature = $feature)] pub mod $mod_name { pub use super::$schema_mod::*; @@ -37,6 +41,7 @@ define_schema_version!( mcp_2025_06_18, "generated_schema/2025_06_18/mcp_schema.rs", "generated_schema/2025_06_18/schema_utils.rs", + "generated_schema/2025_06_18/validators.rs", __int_2025_06_18, __int_utils_2025_06_18 ); @@ -47,6 +52,7 @@ define_schema_version!( mcp_2025_03_26, "generated_schema/2025_03_26/mcp_schema.rs", "generated_schema/2025_03_26/schema_utils.rs", + "generated_schema/2025_03_26/validators.rs", __int_2025_03_26, __int_utils_2025_03_26 ); @@ -57,6 +63,7 @@ define_schema_version!( mcp_2024_11_05, "generated_schema/2024_11_05/mcp_schema.rs", "generated_schema/2024_11_05/schema_utils.rs", + "generated_schema/2024_11_05/validators.rs", __int_2024_11_05, __int_utils_2024_11_05 ); @@ -67,6 +74,7 @@ define_schema_version!( mcp_draft, "generated_schema/draft/mcp_schema.rs", "generated_schema/draft/schema_utils.rs", + "generated_schema/draft/validators.rs", __int_draft, __int_utils_draft ); diff --git a/src/generated_schema/2024_11_05/mcp_schema.rs b/src/generated_schema/2024_11_05/mcp_schema.rs index b45788a..aeffa9f 100644 --- a/src/generated_schema/2024_11_05/mcp_schema.rs +++ b/src/generated_schema/2024_11_05/mcp_schema.rs @@ -1,14 +1,15 @@ /// ---------------------------------------------------------------------------- -/// This file is auto-generated by mcp-schema-gen v0.4.2. +/// This file is auto-generated by mcp-schema-gen v0.4.4. /// WARNING: /// It is not recommended to modify this file directly. You are free to /// modify or extend the implementations as needed, but please do so at your own risk. /// /// Generated from : -/// Hash : 3473e6e72b222f44163b41eab8d4b0973ac106b6 -/// Generated at : 2025-09-17 19:15:48 +/// Hash : 71204c22f8e062795dca00a87d31070033572ba2 +/// Generated at : 2025-10-03 15:17:01 /// ---------------------------------------------------------------------------- /// +use super::validators as validate; /// MCP Protocol Version pub const LATEST_PROTOCOL_VERSION: &str = "2024-11-05"; /// JSON-RPC Version @@ -171,6 +172,7 @@ pub struct BlobResourceContents { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CallToolRequest { + #[serde(deserialize_with = "validate::call_tool_request_method")] method: ::std::string::String, pub params: CallToolRequestParams, } @@ -359,6 +361,7 @@ A client MUST NOT attempt to cancel its initialize request.*/ /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CancelledNotification { + #[serde(deserialize_with = "validate::cancelled_notification_method")] method: ::std::string::String, pub params: CancelledNotificationParams, } @@ -766,6 +769,7 @@ impl ::std::convert::From for ClientResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CompleteRequest { + #[serde(deserialize_with = "validate::complete_request_method")] method: ::std::string::String, pub params: CompleteRequestParams, } @@ -1063,6 +1067,7 @@ pub struct CompleteResultCompletion { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CreateMessageRequest { + #[serde(deserialize_with = "validate::create_message_request_method")] method: ::std::string::String, pub params: CreateMessageRequestParams, } @@ -1358,7 +1363,7 @@ pub struct EmbeddedResource { #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub annotations: ::std::option::Option, pub resource: EmbeddedResourceResource, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::embedded_resource_type_")] type_: ::std::string::String, } impl EmbeddedResource { @@ -1498,6 +1503,7 @@ pub struct EmptyResult(pub Result); /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct GetPromptRequest { + #[serde(deserialize_with = "validate::get_prompt_request_method")] method: ::std::string::String, pub params: GetPromptRequestParams, } @@ -1648,7 +1654,7 @@ pub struct ImageContent { ///The MIME type of the image. Different providers may support different image types. #[serde(rename = "mimeType")] pub mime_type: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::image_content_type_")] type_: ::std::string::String, } impl ImageContent { @@ -1776,6 +1782,7 @@ pub struct Implementation { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializeRequest { + #[serde(deserialize_with = "validate::initialize_request_method")] method: ::std::string::String, pub params: InitializeRequestParams, } @@ -1915,6 +1922,7 @@ pub struct InitializeResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializedNotification { + #[serde(deserialize_with = "validate::initialized_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2008,6 +2016,7 @@ pub struct InitializedNotificationParams { pub struct JsonrpcError { pub error: RpcError, pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_error_jsonrpc")] jsonrpc: ::std::string::String, } impl JsonrpcError { @@ -2110,6 +2119,7 @@ impl ::std::convert::From for JsonrpcMessage { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcNotification { + #[serde(deserialize_with = "validate::jsonrpc_notification_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -2199,6 +2209,7 @@ pub struct JsonrpcNotificationParams { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_request_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -2299,6 +2310,7 @@ pub struct JsonrpcRequestParamsMeta { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcResponse { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_response_jsonrpc")] jsonrpc: ::std::string::String, pub result: Result, } @@ -2345,6 +2357,7 @@ impl JsonrpcResponse { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListPromptsRequest { + #[serde(deserialize_with = "validate::list_prompts_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2459,6 +2472,7 @@ pub struct ListPromptsResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourceTemplatesRequest { + #[serde(deserialize_with = "validate::list_resource_templates_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2574,6 +2588,7 @@ pub struct ListResourceTemplatesResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourcesRequest { + #[serde(deserialize_with = "validate::list_resources_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2699,6 +2714,7 @@ structure or access specific locations that the client has permission to read fr /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListRootsRequest { + #[serde(deserialize_with = "validate::list_roots_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2835,6 +2851,7 @@ pub struct ListRootsResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListToolsRequest { + #[serde(deserialize_with = "validate::list_tools_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3017,6 +3034,7 @@ impl ::std::fmt::Display for LoggingLevel { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct LoggingMessageNotification { + #[serde(deserialize_with = "validate::logging_message_notification_method")] method: ::std::string::String, pub params: LoggingMessageNotificationParams, } @@ -3353,6 +3371,7 @@ pub struct PaginatedResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PingRequest { + #[serde(deserialize_with = "validate::ping_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3466,6 +3485,7 @@ pub struct PingRequestParamsMeta { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ProgressNotification { + #[serde(deserialize_with = "validate::progress_notification_method")] method: ::std::string::String, pub params: ProgressNotificationParams, } @@ -3659,6 +3679,7 @@ pub struct PromptArgument { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PromptListChangedNotification { + #[serde(deserialize_with = "validate::prompt_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3814,7 +3835,7 @@ impl ::std::convert::From for PromptMessageContent { pub struct PromptReference { ///The name of the prompt or prompt template pub name: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::prompt_reference_type_")] type_: ::std::string::String, } impl PromptReference { @@ -3867,6 +3888,7 @@ impl PromptReference { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ReadResourceRequest { + #[serde(deserialize_with = "validate::read_resource_request_method")] method: ::std::string::String, pub params: ReadResourceRequestParams, } @@ -4275,6 +4297,7 @@ pub struct ResourceContents { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceListChangedNotification { + #[serde(deserialize_with = "validate::resource_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -4347,7 +4370,7 @@ pub struct ResourceListChangedNotificationParams { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceReference { - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::resource_reference_type_")] type_: ::std::string::String, ///The URI or URI template of the resource. pub uri: ::std::string::String, @@ -4506,6 +4529,7 @@ pub struct ResourceTemplateAnnotations { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceUpdatedNotification { + #[serde(deserialize_with = "validate::resource_updated_notification_method")] method: ::std::string::String, pub params: ResourceUpdatedNotificationParams, } @@ -4676,6 +4700,7 @@ The server should then request an updated list of roots using the ListRootsReque /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct RootsListChangedNotification { + #[serde(deserialize_with = "validate::roots_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -5078,6 +5103,7 @@ impl ::std::convert::From for ServerNotification { /// #[derive(::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerRequest { PingRequest(PingRequest), CreateMessageRequest(CreateMessageRequest), @@ -5141,6 +5167,7 @@ impl ::std::convert::From for ServerRequest { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerResult { InitializeResult(InitializeResult), ListResourcesResult(ListResourcesResult), @@ -5238,6 +5265,7 @@ impl ::std::convert::From for ServerResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SetLevelRequest { + #[serde(deserialize_with = "validate::set_level_request_method")] method: ::std::string::String, pub params: SetLevelRequestParams, } @@ -5315,6 +5343,7 @@ pub struct SetLevelRequestParams { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SubscribeRequest { + #[serde(deserialize_with = "validate::subscribe_request_method")] method: ::std::string::String, pub params: SubscribeRequestParams, } @@ -5406,7 +5435,7 @@ pub struct TextContent { pub annotations: ::std::option::Option, ///The text content of the message. pub text: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::text_content_type_")] type_: ::std::string::String, } impl TextContent { @@ -5599,7 +5628,7 @@ pub struct ToolInputSchema { >, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::tool_input_schema_type_")] type_: ::std::string::String, } impl ToolInputSchema { @@ -5658,6 +5687,7 @@ impl ToolInputSchema { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ToolListChangedNotification { + #[serde(deserialize_with = "validate::tool_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -5738,6 +5768,7 @@ pub struct ToolListChangedNotificationParams { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct UnsubscribeRequest { + #[serde(deserialize_with = "validate::unsubscribe_request_method")] method: ::std::string::String, pub params: UnsubscribeRequestParams, } diff --git a/src/generated_schema/2024_11_05/validators.rs b/src/generated_schema/2024_11_05/validators.rs new file mode 100644 index 0000000..8095a9a --- /dev/null +++ b/src/generated_schema/2024_11_05/validators.rs @@ -0,0 +1,166 @@ +/// Validates that a deserialized string field matches a given constant value. +/// +/// This function is intended for use with `#[serde(deserialize_with)]` to enforce +/// that a field in a struct always has a fixed, expected string value during deserialization. +/// +/// # Parameters +/// - `struct_name`: The name of the struct where this validation is applied. +/// - `field_name`: The name of the field being validated. +/// - `expected`: The expected constant string value for the field. +/// - `deserializer`: The Serde deserializer for the field. +/// +/// # Returns +/// - `Ok(String)` if the deserialized value matches the expected value. +/// - `Err(D::Error)` if the value differs, with an error message indicating +/// which struct and field failed validation. +/// +pub fn const_str_validator<'de, D>( + struct_name: &'static str, + field_name: &'static str, + expected: &'static str, + deserializer: D, +) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let value: String = serde::Deserialize::deserialize(deserializer)?; + if value == expected { + Ok(value) + } else { + Err(serde::de::Error::custom(format!( + "Expected field `{field_name}` in struct `{struct_name}` as const value '{expected}', but got '{value}'", + ))) + } +} + +/// Macro to generate a field-specific validator function for use with Serde. +/// +/// This avoids repetitive boilerplate when you have multiple fields/structs +/// requiring constant string validation. +/// +/// # Syntax +/// ```ignore +/// validate!(fn_name, "StructName", "field_name", "expected_value"); +/// ``` +/// +/// - `fn_name`: The function name to generate. +/// - `StructName`: The name of the struct (for error messages). +/// - `field_name`: The name of the field (for error messages). +/// - `expected_value`: The required constant string value. +/// +macro_rules! validate { + ($func_name:ident, $struct:expr, $field:expr, $expected:expr $(,)?) => { + pub(crate) fn $func_name<'de, D>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + const_str_validator($struct, $field, $expected, deserializer) + } + }; +} + +//* Validator Functions *// +validate!(call_tool_request_method, "CallToolRequest", "method", "tools/call"); +validate!( + cancelled_notification_method, + "CancelledNotification", + "method", + "notifications/cancelled" +); +validate!(complete_request_method, "CompleteRequest", "method", "completion/complete"); +validate!( + create_message_request_method, + "CreateMessageRequest", + "method", + "sampling/createMessage" +); +validate!(embedded_resource_type_, "EmbeddedResource", "type_", "resource"); +validate!(get_prompt_request_method, "GetPromptRequest", "method", "prompts/get"); +validate!(image_content_type_, "ImageContent", "type_", "image"); +validate!(initialize_request_method, "InitializeRequest", "method", "initialize"); +validate!( + initialized_notification_method, + "InitializedNotification", + "method", + "notifications/initialized" +); +validate!(jsonrpc_error_jsonrpc, "JsonrpcError", "jsonrpc", "2.0"); +validate!(jsonrpc_notification_jsonrpc, "JsonrpcNotification", "jsonrpc", "2.0"); +validate!(jsonrpc_request_jsonrpc, "JsonrpcRequest", "jsonrpc", "2.0"); +validate!(jsonrpc_response_jsonrpc, "JsonrpcResponse", "jsonrpc", "2.0"); +validate!(list_prompts_request_method, "ListPromptsRequest", "method", "prompts/list"); +validate!( + list_resource_templates_request_method, + "ListResourceTemplatesRequest", + "method", + "resources/templates/list" +); +validate!( + list_resources_request_method, + "ListResourcesRequest", + "method", + "resources/list" +); +validate!(list_roots_request_method, "ListRootsRequest", "method", "roots/list"); +validate!(list_tools_request_method, "ListToolsRequest", "method", "tools/list"); +validate!( + logging_message_notification_method, + "LoggingMessageNotification", + "method", + "notifications/message" +); +validate!(ping_request_method, "PingRequest", "method", "ping"); +validate!( + progress_notification_method, + "ProgressNotification", + "method", + "notifications/progress" +); +validate!( + prompt_list_changed_notification_method, + "PromptListChangedNotification", + "method", + "notifications/prompts/list_changed" +); +validate!(prompt_reference_type_, "PromptReference", "type_", "ref/prompt"); +validate!( + read_resource_request_method, + "ReadResourceRequest", + "method", + "resources/read" +); +validate!( + resource_list_changed_notification_method, + "ResourceListChangedNotification", + "method", + "notifications/resources/list_changed" +); +validate!(resource_reference_type_, "ResourceReference", "type_", "ref/resource"); +validate!( + resource_updated_notification_method, + "ResourceUpdatedNotification", + "method", + "notifications/resources/updated" +); +validate!( + roots_list_changed_notification_method, + "RootsListChangedNotification", + "method", + "notifications/roots/list_changed" +); +validate!(set_level_request_method, "SetLevelRequest", "method", "logging/setLevel"); +validate!(subscribe_request_method, "SubscribeRequest", "method", "resources/subscribe"); +validate!(text_content_type_, "TextContent", "type_", "text"); +validate!(tool_input_schema_type_, "ToolInputSchema", "type_", "object"); +validate!( + tool_list_changed_notification_method, + "ToolListChangedNotification", + "method", + "notifications/tools/list_changed" +); +validate!( + unsubscribe_request_method, + "UnsubscribeRequest", + "method", + "resources/unsubscribe" +); diff --git a/src/generated_schema/2025_03_26/mcp_schema.rs b/src/generated_schema/2025_03_26/mcp_schema.rs index 12af6b0..6387502 100644 --- a/src/generated_schema/2025_03_26/mcp_schema.rs +++ b/src/generated_schema/2025_03_26/mcp_schema.rs @@ -1,14 +1,15 @@ /// ---------------------------------------------------------------------------- -/// This file is auto-generated by mcp-schema-gen v0.4.2. +/// This file is auto-generated by mcp-schema-gen v0.4.4. /// WARNING: /// It is not recommended to modify this file directly. You are free to /// modify or extend the implementations as needed, but please do so at your own risk. /// /// Generated from : -/// Hash : 3473e6e72b222f44163b41eab8d4b0973ac106b6 -/// Generated at : 2025-09-17 19:15:50 +/// Hash : 71204c22f8e062795dca00a87d31070033572ba2 +/// Generated at : 2025-10-03 15:17:03 /// ---------------------------------------------------------------------------- /// +use super::validators as validate; /// MCP Protocol Version pub const LATEST_PROTOCOL_VERSION: &str = "2025-03-26"; /// JSON-RPC Version @@ -103,7 +104,7 @@ pub struct AudioContent { ///The MIME type of the audio. Different providers may support different audio types. #[serde(rename = "mimeType")] pub mime_type: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::audio_content_type_")] type_: ::std::string::String, } impl AudioContent { @@ -204,6 +205,7 @@ pub struct BlobResourceContents { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CallToolRequest { + #[serde(deserialize_with = "validate::call_tool_request_method")] method: ::std::string::String, pub params: CallToolRequestParams, } @@ -404,6 +406,7 @@ A client MUST NOT attempt to cancel its initialize request.*/ /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CancelledNotification { + #[serde(deserialize_with = "validate::cancelled_notification_method")] method: ::std::string::String, pub params: CancelledNotificationParams, } @@ -811,6 +814,7 @@ impl ::std::convert::From for ClientResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CompleteRequest { + #[serde(deserialize_with = "validate::complete_request_method")] method: ::std::string::String, pub params: CompleteRequestParams, } @@ -1108,6 +1112,7 @@ pub struct CompleteResultCompletion { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CreateMessageRequest { + #[serde(deserialize_with = "validate::create_message_request_method")] method: ::std::string::String, pub params: CreateMessageRequestParams, } @@ -1402,7 +1407,7 @@ pub struct EmbeddedResource { #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub annotations: ::std::option::Option, pub resource: EmbeddedResourceResource, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::embedded_resource_type_")] type_: ::std::string::String, } impl EmbeddedResource { @@ -1508,6 +1513,7 @@ pub struct EmptyResult(pub Result); /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct GetPromptRequest { + #[serde(deserialize_with = "validate::get_prompt_request_method")] method: ::std::string::String, pub params: GetPromptRequestParams, } @@ -1645,7 +1651,7 @@ pub struct ImageContent { ///The MIME type of the image. Different providers may support different image types. #[serde(rename = "mimeType")] pub mime_type: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::image_content_type_")] type_: ::std::string::String, } impl ImageContent { @@ -1739,6 +1745,7 @@ pub struct Implementation { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializeRequest { + #[serde(deserialize_with = "validate::initialize_request_method")] method: ::std::string::String, pub params: InitializeRequestParams, } @@ -1878,6 +1885,7 @@ pub struct InitializeResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializedNotification { + #[serde(deserialize_with = "validate::initialized_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2085,6 +2093,7 @@ impl ::std::convert::From for JsonrpcBatchResponseItem { pub struct JsonrpcError { pub error: RpcError, pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_error_jsonrpc")] jsonrpc: ::std::string::String, } impl JsonrpcError { @@ -2206,6 +2215,7 @@ impl ::std::convert::From for JsonrpcMessage { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcNotification { + #[serde(deserialize_with = "validate::jsonrpc_notification_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -2295,6 +2305,7 @@ pub struct JsonrpcNotificationParams { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_request_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -2395,6 +2406,7 @@ pub struct JsonrpcRequestParamsMeta { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcResponse { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_response_jsonrpc")] jsonrpc: ::std::string::String, pub result: Result, } @@ -2441,6 +2453,7 @@ impl JsonrpcResponse { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListPromptsRequest { + #[serde(deserialize_with = "validate::list_prompts_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2555,6 +2568,7 @@ pub struct ListPromptsResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourceTemplatesRequest { + #[serde(deserialize_with = "validate::list_resource_templates_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2670,6 +2684,7 @@ pub struct ListResourceTemplatesResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourcesRequest { + #[serde(deserialize_with = "validate::list_resources_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2795,6 +2810,7 @@ structure or access specific locations that the client has permission to read fr /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListRootsRequest { + #[serde(deserialize_with = "validate::list_roots_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2931,6 +2947,7 @@ pub struct ListRootsResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListToolsRequest { + #[serde(deserialize_with = "validate::list_tools_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3113,6 +3130,7 @@ impl ::std::fmt::Display for LoggingLevel { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct LoggingMessageNotification { + #[serde(deserialize_with = "validate::logging_message_notification_method")] method: ::std::string::String, pub params: LoggingMessageNotificationParams, } @@ -3449,6 +3467,7 @@ pub struct PaginatedResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PingRequest { + #[serde(deserialize_with = "validate::ping_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3566,6 +3585,7 @@ pub struct PingRequestParamsMeta { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ProgressNotification { + #[serde(deserialize_with = "validate::progress_notification_method")] method: ::std::string::String, pub params: ProgressNotificationParams, } @@ -3766,6 +3786,7 @@ pub struct PromptArgument { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PromptListChangedNotification { + #[serde(deserialize_with = "validate::prompt_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3933,7 +3954,7 @@ impl ::std::convert::From for PromptMessageContent { pub struct PromptReference { ///The name of the prompt or prompt template pub name: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::prompt_reference_type_")] type_: ::std::string::String, } impl PromptReference { @@ -3986,6 +4007,7 @@ impl PromptReference { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ReadResourceRequest { + #[serde(deserialize_with = "validate::read_resource_request_method")] method: ::std::string::String, pub params: ReadResourceRequestParams, } @@ -4347,6 +4369,7 @@ pub struct ResourceContents { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceListChangedNotification { + #[serde(deserialize_with = "validate::resource_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -4419,7 +4442,7 @@ pub struct ResourceListChangedNotificationParams { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceReference { - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::resource_reference_type_")] type_: ::std::string::String, ///The URI or URI template of the resource. pub uri: ::std::string::String, @@ -4531,6 +4554,7 @@ pub struct ResourceTemplate { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceUpdatedNotification { + #[serde(deserialize_with = "validate::resource_updated_notification_method")] method: ::std::string::String, pub params: ResourceUpdatedNotificationParams, } @@ -4701,6 +4725,7 @@ The server should then request an updated list of roots using the ListRootsReque /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct RootsListChangedNotification { + #[serde(deserialize_with = "validate::roots_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -5123,6 +5148,7 @@ impl ::std::convert::From for ServerNotification { /// #[derive(::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerRequest { PingRequest(PingRequest), CreateMessageRequest(CreateMessageRequest), @@ -5186,6 +5212,7 @@ impl ::std::convert::From for ServerRequest { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerResult { InitializeResult(InitializeResult), ListResourcesResult(ListResourcesResult), @@ -5283,6 +5310,7 @@ impl ::std::convert::From for ServerResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SetLevelRequest { + #[serde(deserialize_with = "validate::set_level_request_method")] method: ::std::string::String, pub params: SetLevelRequestParams, } @@ -5360,6 +5388,7 @@ pub struct SetLevelRequestParams { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SubscribeRequest { + #[serde(deserialize_with = "validate::subscribe_request_method")] method: ::std::string::String, pub params: SubscribeRequestParams, } @@ -5438,7 +5467,7 @@ pub struct TextContent { pub annotations: ::std::option::Option, ///The text content of the message. pub text: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::text_content_type_")] type_: ::std::string::String, } impl TextContent { @@ -5672,7 +5701,7 @@ pub struct ToolInputSchema { >, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::tool_input_schema_type_")] type_: ::std::string::String, } impl ToolInputSchema { @@ -5731,6 +5760,7 @@ impl ToolInputSchema { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ToolListChangedNotification { + #[serde(deserialize_with = "validate::tool_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -5811,6 +5841,7 @@ pub struct ToolListChangedNotificationParams { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct UnsubscribeRequest { + #[serde(deserialize_with = "validate::unsubscribe_request_method")] method: ::std::string::String, pub params: UnsubscribeRequestParams, } diff --git a/src/generated_schema/2025_03_26/validators.rs b/src/generated_schema/2025_03_26/validators.rs new file mode 100644 index 0000000..c5c3c89 --- /dev/null +++ b/src/generated_schema/2025_03_26/validators.rs @@ -0,0 +1,167 @@ +/// Validates that a deserialized string field matches a given constant value. +/// +/// This function is intended for use with `#[serde(deserialize_with)]` to enforce +/// that a field in a struct always has a fixed, expected string value during deserialization. +/// +/// # Parameters +/// - `struct_name`: The name of the struct where this validation is applied. +/// - `field_name`: The name of the field being validated. +/// - `expected`: The expected constant string value for the field. +/// - `deserializer`: The Serde deserializer for the field. +/// +/// # Returns +/// - `Ok(String)` if the deserialized value matches the expected value. +/// - `Err(D::Error)` if the value differs, with an error message indicating +/// which struct and field failed validation. +/// +pub fn const_str_validator<'de, D>( + struct_name: &'static str, + field_name: &'static str, + expected: &'static str, + deserializer: D, +) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let value: String = serde::Deserialize::deserialize(deserializer)?; + if value == expected { + Ok(value) + } else { + Err(serde::de::Error::custom(format!( + "Expected field `{field_name}` in struct `{struct_name}` as const value '{expected}', but got '{value}'", + ))) + } +} + +/// Macro to generate a field-specific validator function for use with Serde. +/// +/// This avoids repetitive boilerplate when you have multiple fields/structs +/// requiring constant string validation. +/// +/// # Syntax +/// ```ignore +/// validate!(fn_name, "StructName", "field_name", "expected_value"); +/// ``` +/// +/// - `fn_name`: The function name to generate. +/// - `StructName`: The name of the struct (for error messages). +/// - `field_name`: The name of the field (for error messages). +/// - `expected_value`: The required constant string value. +/// +macro_rules! validate { + ($func_name:ident, $struct:expr, $field:expr, $expected:expr $(,)?) => { + pub(crate) fn $func_name<'de, D>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + const_str_validator($struct, $field, $expected, deserializer) + } + }; +} + +//* Validator Functions *// +validate!(audio_content_type_, "AudioContent", "type_", "audio"); +validate!(call_tool_request_method, "CallToolRequest", "method", "tools/call"); +validate!( + cancelled_notification_method, + "CancelledNotification", + "method", + "notifications/cancelled" +); +validate!(complete_request_method, "CompleteRequest", "method", "completion/complete"); +validate!( + create_message_request_method, + "CreateMessageRequest", + "method", + "sampling/createMessage" +); +validate!(embedded_resource_type_, "EmbeddedResource", "type_", "resource"); +validate!(get_prompt_request_method, "GetPromptRequest", "method", "prompts/get"); +validate!(image_content_type_, "ImageContent", "type_", "image"); +validate!(initialize_request_method, "InitializeRequest", "method", "initialize"); +validate!( + initialized_notification_method, + "InitializedNotification", + "method", + "notifications/initialized" +); +validate!(jsonrpc_error_jsonrpc, "JsonrpcError", "jsonrpc", "2.0"); +validate!(jsonrpc_notification_jsonrpc, "JsonrpcNotification", "jsonrpc", "2.0"); +validate!(jsonrpc_request_jsonrpc, "JsonrpcRequest", "jsonrpc", "2.0"); +validate!(jsonrpc_response_jsonrpc, "JsonrpcResponse", "jsonrpc", "2.0"); +validate!(list_prompts_request_method, "ListPromptsRequest", "method", "prompts/list"); +validate!( + list_resource_templates_request_method, + "ListResourceTemplatesRequest", + "method", + "resources/templates/list" +); +validate!( + list_resources_request_method, + "ListResourcesRequest", + "method", + "resources/list" +); +validate!(list_roots_request_method, "ListRootsRequest", "method", "roots/list"); +validate!(list_tools_request_method, "ListToolsRequest", "method", "tools/list"); +validate!( + logging_message_notification_method, + "LoggingMessageNotification", + "method", + "notifications/message" +); +validate!(ping_request_method, "PingRequest", "method", "ping"); +validate!( + progress_notification_method, + "ProgressNotification", + "method", + "notifications/progress" +); +validate!( + prompt_list_changed_notification_method, + "PromptListChangedNotification", + "method", + "notifications/prompts/list_changed" +); +validate!(prompt_reference_type_, "PromptReference", "type_", "ref/prompt"); +validate!( + read_resource_request_method, + "ReadResourceRequest", + "method", + "resources/read" +); +validate!( + resource_list_changed_notification_method, + "ResourceListChangedNotification", + "method", + "notifications/resources/list_changed" +); +validate!(resource_reference_type_, "ResourceReference", "type_", "ref/resource"); +validate!( + resource_updated_notification_method, + "ResourceUpdatedNotification", + "method", + "notifications/resources/updated" +); +validate!( + roots_list_changed_notification_method, + "RootsListChangedNotification", + "method", + "notifications/roots/list_changed" +); +validate!(set_level_request_method, "SetLevelRequest", "method", "logging/setLevel"); +validate!(subscribe_request_method, "SubscribeRequest", "method", "resources/subscribe"); +validate!(text_content_type_, "TextContent", "type_", "text"); +validate!(tool_input_schema_type_, "ToolInputSchema", "type_", "object"); +validate!( + tool_list_changed_notification_method, + "ToolListChangedNotification", + "method", + "notifications/tools/list_changed" +); +validate!( + unsubscribe_request_method, + "UnsubscribeRequest", + "method", + "resources/unsubscribe" +); diff --git a/src/generated_schema/2025_06_18/mcp_schema.rs b/src/generated_schema/2025_06_18/mcp_schema.rs index b7827a2..8154108 100644 --- a/src/generated_schema/2025_06_18/mcp_schema.rs +++ b/src/generated_schema/2025_06_18/mcp_schema.rs @@ -1,14 +1,15 @@ /// ---------------------------------------------------------------------------- -/// This file is auto-generated by mcp-schema-gen v0.4.2. +/// This file is auto-generated by mcp-schema-gen v0.4.4. /// WARNING: /// It is not recommended to modify this file directly. You are free to /// modify or extend the implementations as needed, but please do so at your own risk. /// /// Generated from : -/// Hash : 3473e6e72b222f44163b41eab8d4b0973ac106b6 -/// Generated at : 2025-09-17 19:15:50 +/// Hash : 71204c22f8e062795dca00a87d31070033572ba2 +/// Generated at : 2025-10-03 15:17:04 /// ---------------------------------------------------------------------------- /// +use super::validators as validate; /// MCP Protocol Version pub const LATEST_PROTOCOL_VERSION: &str = "2025-06-18"; /// JSON-RPC Version @@ -121,7 +122,7 @@ pub struct AudioContent { ///The MIME type of the audio. Different providers may support different audio types. #[serde(rename = "mimeType")] pub mime_type: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::audio_content_type_")] type_: ::std::string::String, } impl AudioContent { @@ -266,7 +267,7 @@ pub struct BooleanSchema { pub description: ::std::option::Option<::std::string::String>, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::boolean_schema_type_")] type_: ::std::string::String, } impl BooleanSchema { @@ -327,6 +328,7 @@ impl BooleanSchema { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CallToolRequest { + #[serde(deserialize_with = "validate::call_tool_request_method")] method: ::std::string::String, pub params: CallToolRequestParams, } @@ -477,6 +479,7 @@ A client MUST NOT attempt to cancel its initialize request.*/ /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CancelledNotification { + #[serde(deserialize_with = "validate::cancelled_notification_method")] method: ::std::string::String, pub params: CancelledNotificationParams, } @@ -914,6 +917,7 @@ impl ::std::convert::From for ClientResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CompleteRequest { + #[serde(deserialize_with = "validate::complete_request_method")] method: ::std::string::String, pub params: CompleteRequestParams, } @@ -1273,7 +1277,7 @@ impl ::std::convert::From for ContentBlock { /// ] /// }, /// "maxTokens": { -/// "description": "The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested.", +/// "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", /// "type": "integer" /// }, /// "messages": { @@ -1312,8 +1316,7 @@ impl ::std::convert::From for ContentBlock { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CreateMessageRequest { - // This field requires custom deserialization for validation. - #[serde(deserialize_with = "server_request_method_validation::deserialize_CreateMessageRequest_method")] + #[serde(deserialize_with = "validate::create_message_request_method")] method: ::std::string::String, pub params: CreateMessageRequestParams, } @@ -1353,7 +1356,7 @@ impl CreateMessageRequest { /// ] /// }, /// "maxTokens": { -/// "description": "The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested.", +/// "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", /// "type": "integer" /// }, /// "messages": { @@ -1393,7 +1396,8 @@ pub struct CreateMessageRequestParams { ///A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. #[serde(rename = "includeContext", default, skip_serializing_if = "::std::option::Option::is_none")] pub include_context: ::std::option::Option, - ///The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested. + /**The requested maximum number of tokens to sample (to prevent runaway completions). + The client MAY choose to sample fewer tokens than the requested maximum.*/ #[serde(rename = "maxTokens")] pub max_tokens: i64, pub messages: ::std::vec::Vec, @@ -1627,8 +1631,7 @@ pub struct Cursor(pub ::std::string::String); /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ElicitRequest { - // This field requires custom deserialization for validation. - #[serde(deserialize_with = "server_request_method_validation::deserialize_ElicitRequest_method")] + #[serde(deserialize_with = "validate::elicit_request_method")] method: ::std::string::String, pub params: ElicitRequestParams, } @@ -1738,7 +1741,7 @@ pub struct ElicitRequestedSchema { pub properties: ::std::collections::HashMap<::std::string::String, PrimitiveSchemaDefinition>, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::elicit_requested_schema_type_")] type_: ::std::string::String, } impl ElicitRequestedSchema { @@ -1934,7 +1937,7 @@ pub struct EmbeddedResource { #[serde(rename = "_meta", default, skip_serializing_if = "::std::option::Option::is_none")] pub meta: ::std::option::Option<::serde_json::Map<::std::string::String, ::serde_json::Value>>, pub resource: EmbeddedResourceResource, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::embedded_resource_type_")] type_: ::std::string::String, } impl EmbeddedResource { @@ -2051,7 +2054,7 @@ pub struct EnumSchema { pub enum_names: ::std::vec::Vec<::std::string::String>, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::enum_schema_type_")] type_: ::std::string::String, } impl EnumSchema { @@ -2118,6 +2121,7 @@ impl EnumSchema { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct GetPromptRequest { + #[serde(deserialize_with = "validate::get_prompt_request_method")] method: ::std::string::String, pub params: GetPromptRequestParams, } @@ -2263,7 +2267,7 @@ pub struct ImageContent { ///The MIME type of the image. Different providers may support different image types. #[serde(rename = "mimeType")] pub mime_type: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::image_content_type_")] type_: ::std::string::String, } impl ImageContent { @@ -2372,6 +2376,7 @@ pub struct Implementation { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializeRequest { + #[serde(deserialize_with = "validate::initialize_request_method")] method: ::std::string::String, pub params: InitializeRequestParams, } @@ -2511,6 +2516,7 @@ pub struct InitializeResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializedNotification { + #[serde(deserialize_with = "validate::initialized_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2604,6 +2610,7 @@ pub struct InitializedNotificationParams { pub struct JsonrpcError { pub error: RpcError, pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_error_jsonrpc")] jsonrpc: ::std::string::String, } impl JsonrpcError { @@ -2707,6 +2714,7 @@ impl ::std::convert::From for JsonrpcMessage { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcNotification { + #[serde(deserialize_with = "validate::jsonrpc_notification_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -2798,6 +2806,7 @@ pub struct JsonrpcNotificationParams { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_request_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -2904,6 +2913,7 @@ pub struct JsonrpcRequestParamsMeta { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcResponse { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_response_jsonrpc")] jsonrpc: ::std::string::String, pub result: Result, } @@ -2950,6 +2960,7 @@ impl JsonrpcResponse { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListPromptsRequest { + #[serde(deserialize_with = "validate::list_prompts_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3064,6 +3075,7 @@ pub struct ListPromptsResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourceTemplatesRequest { + #[serde(deserialize_with = "validate::list_resource_templates_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3179,6 +3191,7 @@ pub struct ListResourceTemplatesResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourcesRequest { + #[serde(deserialize_with = "validate::list_resources_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3306,8 +3319,7 @@ structure or access specific locations that the client has permission to read fr /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListRootsRequest { - // This field requires custom deserialization for validation. - #[serde(deserialize_with = "server_request_method_validation::deserialize_ListRootsRequest_method")] + #[serde(deserialize_with = "validate::list_roots_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3450,6 +3462,7 @@ pub struct ListRootsResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListToolsRequest { + #[serde(deserialize_with = "validate::list_tools_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3632,6 +3645,7 @@ impl ::std::fmt::Display for LoggingLevel { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct LoggingMessageNotification { + #[serde(deserialize_with = "validate::logging_message_notification_method")] method: ::std::string::String, pub params: LoggingMessageNotificationParams, } @@ -4046,8 +4060,7 @@ pub struct PaginatedResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PingRequest { - // This field requires custom deserialization for validation. - #[serde(deserialize_with = "server_request_method_validation::deserialize_PingRequest_method")] + #[serde(deserialize_with = "validate::ping_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -4224,6 +4237,7 @@ impl ::std::convert::From for PrimitiveSchemaDefinition { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ProgressNotification { + #[serde(deserialize_with = "validate::progress_notification_method")] method: ::std::string::String, pub params: ProgressNotificationParams, } @@ -4454,6 +4468,7 @@ pub struct PromptArgument { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PromptListChangedNotification { + #[serde(deserialize_with = "validate::prompt_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -4568,7 +4583,7 @@ pub struct PromptReference { if present).*/ #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::prompt_reference_type_")] type_: ::std::string::String, } impl PromptReference { @@ -4622,6 +4637,7 @@ impl PromptReference { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ReadResourceRequest { + #[serde(deserialize_with = "validate::read_resource_request_method")] method: ::std::string::String, pub params: ReadResourceRequestParams, } @@ -5069,7 +5085,7 @@ pub struct ResourceLink { if present).*/ #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::resource_link_type_")] type_: ::std::string::String, ///The URI of this resource. pub uri: ::std::string::String, @@ -5138,6 +5154,7 @@ impl ResourceLink { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceListChangedNotification { + #[serde(deserialize_with = "validate::resource_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -5285,7 +5302,7 @@ pub struct ResourceTemplate { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceTemplateReference { - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::resource_template_reference_type_")] type_: ::std::string::String, ///The URI or URI template of the resource. pub uri: ::std::string::String, @@ -5340,6 +5357,7 @@ impl ResourceTemplateReference { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceUpdatedNotification { + #[serde(deserialize_with = "validate::resource_updated_notification_method")] method: ::std::string::String, pub params: ResourceUpdatedNotificationParams, } @@ -5518,6 +5536,7 @@ The server should then request an updated list of roots using the ListRootsReque /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct RootsListChangedNotification { + #[serde(deserialize_with = "validate::roots_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -5943,6 +5962,7 @@ impl ::std::convert::From for ServerNotification { /// #[derive(::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerRequest { PingRequest(PingRequest), CreateMessageRequest(CreateMessageRequest), @@ -6012,6 +6032,7 @@ impl ::std::convert::From for ServerRequest { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerResult { InitializeResult(InitializeResult), ListResourcesResult(ListResourcesResult), @@ -6109,6 +6130,7 @@ impl ::std::convert::From for ServerResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SetLevelRequest { + #[serde(deserialize_with = "validate::set_level_request_method")] method: ::std::string::String, pub params: SetLevelRequestParams, } @@ -6202,7 +6224,7 @@ pub struct StringSchema { pub min_length: ::std::option::Option, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::string_schema_type_")] type_: ::std::string::String, } impl StringSchema { @@ -6302,6 +6324,7 @@ impl ::std::fmt::Display for StringSchemaFormat { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SubscribeRequest { + #[serde(deserialize_with = "validate::subscribe_request_method")] method: ::std::string::String, pub params: SubscribeRequestParams, } @@ -6388,7 +6411,7 @@ pub struct TextContent { pub meta: ::std::option::Option<::serde_json::Map<::std::string::String, ::serde_json::Value>>, ///The text content of the message. pub text: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::text_content_type_")] type_: ::std::string::String, } impl TextContent { @@ -6683,7 +6706,7 @@ pub struct ToolInputSchema { >, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::tool_input_schema_type_")] type_: ::std::string::String, } impl ToolInputSchema { @@ -6742,6 +6765,7 @@ impl ToolInputSchema { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ToolListChangedNotification { + #[serde(deserialize_with = "validate::tool_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -6828,7 +6852,7 @@ pub struct ToolOutputSchema { >, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::tool_output_schema_type_")] type_: ::std::string::String, } impl ToolOutputSchema { @@ -6890,6 +6914,7 @@ impl ToolOutputSchema { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct UnsubscribeRequest { + #[serde(deserialize_with = "validate::unsubscribe_request_method")] method: ::std::string::String, pub params: UnsubscribeRequestParams, } @@ -7175,112 +7200,6 @@ impl ServerNotification { } } } - -// Custom module for deserialization function to prevent name conflicts. -mod server_request_method_validation{ - - // Custom deserialization function, following the `deserialize_#StructName_#FieldName` format. - #[allow(non_snake_case)] - pub(super) fn deserialize_PingRequest_method<'de, D>( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - let value = serde::Deserialize::deserialize(deserializer)?; - // The expected constant value. - let expected = "ping"; - - // Validate the deserialized value. - if value == expected { - Ok(value) - } else { - // The error message with format - // "Expected field `#FieldName` in struct `#StructName` as const value '{}', but got '{}'" - Err(serde::de::Error::custom(format!( - "Expected field `method` in struct `PingRequest` as const value '{}', but got '{}'", - expected, value - ))) - } - } - - // Custom deserialization function, following the `deserialize_#StructName_#FieldName` format. - #[allow(non_snake_case)] - pub(super) fn deserialize_CreateMessageRequest_method<'de, D>( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - let value = serde::Deserialize::deserialize(deserializer)?; - // The expected constant value. - let expected = "sampling/createMessage"; - - // Validate the deserialized value. - if value == expected { - Ok(value) - } else { - // The error message with format - // "Expected field `#FieldName` in struct `#StructName` as const value '{}', but got '{}'" - Err(serde::de::Error::custom(format!( - "Expected field `method` in struct `CreateMessageRequest` as const value '{}', but got '{}'", - expected, value - ))) - } - } - - // Custom deserialization function, following the `deserialize_#StructName_#FieldName` format. - #[allow(non_snake_case)] - pub(super) fn deserialize_ListRootsRequest_method<'de, D>( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - let value = serde::Deserialize::deserialize(deserializer)?; - // The expected constant value. - let expected = "roots/list"; - - // Validate the deserialized value. - if value == expected { - Ok(value) - } else { - // The error message with format - // "Expected field `#FieldName` in struct `#StructName` as const value '{}', but got '{}'" - Err(serde::de::Error::custom(format!( - "Expected field `method` in struct `ListRootsRequest` as const value '{}', but got '{}'", - expected, value - ))) - } - } - - // Custom deserialization function, following the `deserialize_#StructName_#FieldName` format. - #[allow(non_snake_case)] - pub(super) fn deserialize_ElicitRequest_method<'de, D>( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - let value = serde::Deserialize::deserialize(deserializer)?; - // The expected constant value. - let expected = "elicitation/create"; - - // Validate the deserialized value. - if value == expected { - Ok(value) - } else { - // The error message with format - // "Expected field `#FieldName` in struct `#StructName` as const value '{}', but got '{}'" - Err(serde::de::Error::custom(format!( - "Expected field `method` in struct `ElicitRequest` as const value '{}', but got '{}'", - expected, value - ))) - } - } - -} - #[deprecated(since = "0.3.0", note = "Use `RpcError` instead.")] pub type JsonrpcErrorError = RpcError; #[deprecated(since = "0.7.0", note = "Use `ElicitRequestedSchema` instead.")] diff --git a/src/generated_schema/2025_06_18/schema_utils.rs b/src/generated_schema/2025_06_18/schema_utils.rs index 510ab9c..14f3094 100644 --- a/src/generated_schema/2025_06_18/schema_utils.rs +++ b/src/generated_schema/2025_06_18/schema_utils.rs @@ -4453,12 +4453,5 @@ mod tests { // default let result = detect_message_type(&json!({})); assert!(matches!(result, MessageTypes::Request)); - - // assert method type validation - let should_err:std::result::Result = serde_json::from_value(json!({ - "method":"wrong_method", - "params":null - })); - assert!(should_err.is_err()); } } diff --git a/src/generated_schema/2025_06_18/validators.rs b/src/generated_schema/2025_06_18/validators.rs new file mode 100644 index 0000000..8ef6cdb --- /dev/null +++ b/src/generated_schema/2025_06_18/validators.rs @@ -0,0 +1,179 @@ +/// Validates that a deserialized string field matches a given constant value. +/// +/// This function is intended for use with `#[serde(deserialize_with)]` to enforce +/// that a field in a struct always has a fixed, expected string value during deserialization. +/// +/// # Parameters +/// - `struct_name`: The name of the struct where this validation is applied. +/// - `field_name`: The name of the field being validated. +/// - `expected`: The expected constant string value for the field. +/// - `deserializer`: The Serde deserializer for the field. +/// +/// # Returns +/// - `Ok(String)` if the deserialized value matches the expected value. +/// - `Err(D::Error)` if the value differs, with an error message indicating +/// which struct and field failed validation. +/// +pub fn const_str_validator<'de, D>( + struct_name: &'static str, + field_name: &'static str, + expected: &'static str, + deserializer: D, +) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let value: String = serde::Deserialize::deserialize(deserializer)?; + if value == expected { + Ok(value) + } else { + Err(serde::de::Error::custom(format!( + "Expected field `{field_name}` in struct `{struct_name}` as const value '{expected}', but got '{value}'", + ))) + } +} + +/// Macro to generate a field-specific validator function for use with Serde. +/// +/// This avoids repetitive boilerplate when you have multiple fields/structs +/// requiring constant string validation. +/// +/// # Syntax +/// ```ignore +/// validate!(fn_name, "StructName", "field_name", "expected_value"); +/// ``` +/// +/// - `fn_name`: The function name to generate. +/// - `StructName`: The name of the struct (for error messages). +/// - `field_name`: The name of the field (for error messages). +/// - `expected_value`: The required constant string value. +/// +macro_rules! validate { + ($func_name:ident, $struct:expr, $field:expr, $expected:expr $(,)?) => { + pub(crate) fn $func_name<'de, D>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + const_str_validator($struct, $field, $expected, deserializer) + } + }; +} + +//* Validator Functions *// +validate!(audio_content_type_, "AudioContent", "type_", "audio"); +validate!(boolean_schema_type_, "BooleanSchema", "type_", "boolean"); +validate!(call_tool_request_method, "CallToolRequest", "method", "tools/call"); +validate!( + cancelled_notification_method, + "CancelledNotification", + "method", + "notifications/cancelled" +); +validate!(complete_request_method, "CompleteRequest", "method", "completion/complete"); +validate!( + create_message_request_method, + "CreateMessageRequest", + "method", + "sampling/createMessage" +); +validate!(elicit_request_method, "ElicitRequest", "method", "elicitation/create"); +validate!(elicit_requested_schema_type_, "ElicitRequestedSchema", "type_", "object"); +validate!(embedded_resource_type_, "EmbeddedResource", "type_", "resource"); +validate!(enum_schema_type_, "EnumSchema", "type_", "string"); +validate!(get_prompt_request_method, "GetPromptRequest", "method", "prompts/get"); +validate!(image_content_type_, "ImageContent", "type_", "image"); +validate!(initialize_request_method, "InitializeRequest", "method", "initialize"); +validate!( + initialized_notification_method, + "InitializedNotification", + "method", + "notifications/initialized" +); +validate!(jsonrpc_error_jsonrpc, "JsonrpcError", "jsonrpc", "2.0"); +validate!(jsonrpc_notification_jsonrpc, "JsonrpcNotification", "jsonrpc", "2.0"); +validate!(jsonrpc_request_jsonrpc, "JsonrpcRequest", "jsonrpc", "2.0"); +validate!(jsonrpc_response_jsonrpc, "JsonrpcResponse", "jsonrpc", "2.0"); +validate!(list_prompts_request_method, "ListPromptsRequest", "method", "prompts/list"); +validate!( + list_resource_templates_request_method, + "ListResourceTemplatesRequest", + "method", + "resources/templates/list" +); +validate!( + list_resources_request_method, + "ListResourcesRequest", + "method", + "resources/list" +); +validate!(list_roots_request_method, "ListRootsRequest", "method", "roots/list"); +validate!(list_tools_request_method, "ListToolsRequest", "method", "tools/list"); +validate!( + logging_message_notification_method, + "LoggingMessageNotification", + "method", + "notifications/message" +); +validate!(ping_request_method, "PingRequest", "method", "ping"); +validate!( + progress_notification_method, + "ProgressNotification", + "method", + "notifications/progress" +); +validate!( + prompt_list_changed_notification_method, + "PromptListChangedNotification", + "method", + "notifications/prompts/list_changed" +); +validate!(prompt_reference_type_, "PromptReference", "type_", "ref/prompt"); +validate!( + read_resource_request_method, + "ReadResourceRequest", + "method", + "resources/read" +); +validate!(resource_link_type_, "ResourceLink", "type_", "resource_link"); +validate!( + resource_list_changed_notification_method, + "ResourceListChangedNotification", + "method", + "notifications/resources/list_changed" +); +validate!( + resource_template_reference_type_, + "ResourceTemplateReference", + "type_", + "ref/resource" +); +validate!( + resource_updated_notification_method, + "ResourceUpdatedNotification", + "method", + "notifications/resources/updated" +); +validate!( + roots_list_changed_notification_method, + "RootsListChangedNotification", + "method", + "notifications/roots/list_changed" +); +validate!(set_level_request_method, "SetLevelRequest", "method", "logging/setLevel"); +validate!(string_schema_type_, "StringSchema", "type_", "string"); +validate!(subscribe_request_method, "SubscribeRequest", "method", "resources/subscribe"); +validate!(text_content_type_, "TextContent", "type_", "text"); +validate!(tool_input_schema_type_, "ToolInputSchema", "type_", "object"); +validate!( + tool_list_changed_notification_method, + "ToolListChangedNotification", + "method", + "notifications/tools/list_changed" +); +validate!(tool_output_schema_type_, "ToolOutputSchema", "type_", "object"); +validate!( + unsubscribe_request_method, + "UnsubscribeRequest", + "method", + "resources/unsubscribe" +); diff --git a/src/generated_schema/draft/mcp_schema.rs b/src/generated_schema/draft/mcp_schema.rs index 4d70a59..4c9206d 100644 --- a/src/generated_schema/draft/mcp_schema.rs +++ b/src/generated_schema/draft/mcp_schema.rs @@ -1,14 +1,15 @@ /// ---------------------------------------------------------------------------- -/// This file is auto-generated by mcp-schema-gen v0.4.2. +/// This file is auto-generated by mcp-schema-gen v0.4.4. /// WARNING: /// It is not recommended to modify this file directly. You are free to /// modify or extend the implementations as needed, but please do so at your own risk. /// /// Generated from : -/// Hash : 3473e6e72b222f44163b41eab8d4b0973ac106b6 -/// Generated at : 2025-09-17 19:15:51 +/// Hash : 71204c22f8e062795dca00a87d31070033572ba2 +/// Generated at : 2025-10-03 15:17:04 /// ---------------------------------------------------------------------------- /// +use super::validators as validate; /// MCP Protocol Version pub const LATEST_PROTOCOL_VERSION: &str = "DRAFT-2025-v3"; /// JSON-RPC Version @@ -121,7 +122,7 @@ pub struct AudioContent { ///The MIME type of the audio. Different providers may support different audio types. #[serde(rename = "mimeType")] pub mime_type: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::audio_content_type_")] type_: ::std::string::String, } impl AudioContent { @@ -266,7 +267,7 @@ pub struct BooleanSchema { pub description: ::std::option::Option<::std::string::String>, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::boolean_schema_type_")] type_: ::std::string::String, } impl BooleanSchema { @@ -337,7 +338,9 @@ impl BooleanSchema { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CallToolRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::call_tool_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::call_tool_request_method")] method: ::std::string::String, pub params: CallToolRequestParams, } @@ -498,7 +501,9 @@ A client MUST NOT attempt to cancel its initialize request.*/ /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CancelledNotification { + #[serde(deserialize_with = "validate::cancelled_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::cancelled_notification_method")] method: ::std::string::String, pub params: CancelledNotificationParams, } @@ -950,7 +955,9 @@ impl ::std::convert::From for ClientResult { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CompleteRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::complete_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::complete_request_method")] method: ::std::string::String, pub params: CompleteRequestParams, } @@ -1324,7 +1331,7 @@ impl ::std::convert::From for ContentBlock { /// ] /// }, /// "maxTokens": { -/// "description": "The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested.", +/// "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", /// "type": "integer" /// }, /// "messages": { @@ -1364,7 +1371,9 @@ impl ::std::convert::From for ContentBlock { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct CreateMessageRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::create_message_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::create_message_request_method")] method: ::std::string::String, pub params: CreateMessageRequestParams, } @@ -1409,7 +1418,7 @@ impl CreateMessageRequest { /// ] /// }, /// "maxTokens": { -/// "description": "The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested.", +/// "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", /// "type": "integer" /// }, /// "messages": { @@ -1449,7 +1458,8 @@ pub struct CreateMessageRequestParams { ///A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. #[serde(rename = "includeContext", default, skip_serializing_if = "::std::option::Option::is_none")] pub include_context: ::std::option::Option, - ///The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested. + /**The requested maximum number of tokens to sample (to prevent runaway completions). + The client MAY choose to sample fewer tokens than the requested maximum.*/ #[serde(rename = "maxTokens")] pub max_tokens: i64, pub messages: ::std::vec::Vec, @@ -1693,7 +1703,9 @@ pub struct Cursor(pub ::std::string::String); #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ElicitRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::elicit_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::elicit_request_method")] method: ::std::string::String, pub params: ElicitRequestParams, } @@ -1808,7 +1820,7 @@ pub struct ElicitRequestedSchema { pub properties: ::std::collections::HashMap<::std::string::String, PrimitiveSchemaDefinition>, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::elicit_requested_schema_type_")] type_: ::std::string::String, } impl ElicitRequestedSchema { @@ -2004,7 +2016,7 @@ pub struct EmbeddedResource { #[serde(rename = "_meta", default, skip_serializing_if = "::std::option::Option::is_none")] pub meta: ::std::option::Option<::serde_json::Map<::std::string::String, ::serde_json::Value>>, pub resource: EmbeddedResourceResource, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::embedded_resource_type_")] type_: ::std::string::String, } impl EmbeddedResource { @@ -2126,7 +2138,7 @@ pub struct EnumSchema { pub enum_names: ::std::vec::Vec<::std::string::String>, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::enum_schema_type_")] type_: ::std::string::String, } impl EnumSchema { @@ -2205,7 +2217,9 @@ impl EnumSchema { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct GetPromptRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::get_prompt_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::get_prompt_request_method")] method: ::std::string::String, pub params: GetPromptRequestParams, } @@ -2320,13 +2334,24 @@ pub struct GetPromptResult { /// "type": "string" /// }, /// "sizes": { -/// "description": "Optional string that specifies one or more sizes at which the icon can be used.\nFor example: \"48x48\", \"48x48 96x96\", or \"any\" for scalable formats like SVG.\n\nIf not provided, the client should assume that the icon can be used at any size.", -/// "type": "string" +/// "description": "Optional array of strings that specify sizes at which the icon can be used.\nEach string should be in WxH format (e.g., \"48x48\", \"96x96\") or \"any\" for scalable formats like SVG.\n\nIf not provided, the client should assume that the icon can be used at any size.", +/// "type": "array", +/// "items": { +/// "type": "string" +/// } /// }, /// "src": { /// "description": "A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a\ndata: URI with Base64-encoded image data.\n\nConsumers SHOULD takes steps to ensure URLs serving icons are from the\nsame domain as the client/server or a trusted domain.\n\nConsumers SHOULD take appropriate precautions when consuming SVGs as they can contain\nexecutable JavaScript.", /// "type": "string", /// "format": "uri" +/// }, +/// "theme": { +/// "description": "Optional specifier for the theme this icon is designed for. light indicates\nthe icon is designed to be used with a light background, and dark indicates\nthe icon is designed to be used with a dark background.\n\nIf not provided, the client should assume the icon can be used with any theme.", +/// "type": "string", +/// "enum": [ +/// "dark", +/// "light" +/// ] /// } /// } ///} @@ -2338,11 +2363,11 @@ pub struct Icon { For example: "image/png", "image/jpeg", or "image/svg+xml".*/ #[serde(rename = "mimeType", default, skip_serializing_if = "::std::option::Option::is_none")] pub mime_type: ::std::option::Option<::std::string::String>, - /**Optional string that specifies one or more sizes at which the icon can be used. - For example: "48x48", "48x48 96x96", or "any" for scalable formats like SVG. + /**Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., "48x48", "96x96") or "any" for scalable formats like SVG. If not provided, the client should assume that the icon can be used at any size.*/ - #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] - pub sizes: ::std::option::Option<::std::string::String>, + #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] + pub sizes: ::std::vec::Vec<::std::string::String>, /**A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a data: URI with Base64-encoded image data. Consumers SHOULD takes steps to ensure URLs serving icons are from the @@ -2350,6 +2375,45 @@ pub struct Icon { Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain executable JavaScript.*/ pub src: ::std::string::String, + /**Optional specifier for the theme this icon is designed for. light indicates + the icon is designed to be used with a light background, and dark indicates + the icon is designed to be used with a dark background. + If not provided, the client should assume the icon can be used with any theme.*/ + #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] + pub theme: ::std::option::Option, +} +/**Optional specifier for the theme this icon is designed for. light indicates +the icon is designed to be used with a light background, and dark indicates +the icon is designed to be used with a dark background. +If not provided, the client should assume the icon can be used with any theme.*/ +/// +///
JSON schema +/// +/// ```json +///{ +/// "description": "Optional specifier for the theme this icon is designed for. light indicates\nthe icon is designed to be used with a light background, and dark indicates\nthe icon is designed to be used with a dark background.\n\nIf not provided, the client should assume the icon can be used with any theme.", +/// "type": "string", +/// "enum": [ +/// "dark", +/// "light" +/// ] +///} +/// ``` +///
+#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum IconTheme { + #[serde(rename = "dark")] + Dark, + #[serde(rename = "light")] + Light, +} +impl ::std::fmt::Display for IconTheme { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + Self::Dark => write!(f, "dark"), + Self::Light => write!(f, "light"), + } + } } ///Base interface to add icons property. /// @@ -2436,7 +2500,7 @@ pub struct ImageContent { ///The MIME type of the image. Different providers may support different image types. #[serde(rename = "mimeType")] pub mime_type: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::image_content_type_")] type_: ::std::string::String, } impl ImageContent { @@ -2495,7 +2559,7 @@ impl ImageContent { /// "websiteUrl": { /// "description": "An optional URL of the website for this implementation.", /// "type": "string", -/// "format": ": uri" +/// "format": "uri" /// } /// } ///} @@ -2579,7 +2643,9 @@ pub struct Implementation { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializeRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::initialize_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::initialize_request_method")] method: ::std::string::String, pub params: InitializeRequestParams, } @@ -2729,7 +2795,9 @@ pub struct InitializeResult { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct InitializedNotification { + #[serde(deserialize_with = "validate::initialized_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::initialized_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -2810,6 +2878,7 @@ pub struct InitializedNotificationParams { pub struct JsonrpcError { pub error: RpcError, pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_error_jsonrpc")] jsonrpc: ::std::string::String, } impl JsonrpcError { @@ -2913,6 +2982,7 @@ impl ::std::convert::From for JsonrpcMessage { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcNotification { + #[serde(deserialize_with = "validate::jsonrpc_notification_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -3004,6 +3074,7 @@ pub struct JsonrpcNotificationParams { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_request_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -3110,6 +3181,7 @@ pub struct JsonrpcRequestParamsMeta { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct JsonrpcResponse { pub id: RequestId, + #[serde(deserialize_with = "validate::jsonrpc_response_jsonrpc")] jsonrpc: ::std::string::String, pub result: Result, } @@ -3166,7 +3238,9 @@ impl JsonrpcResponse { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListPromptsRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::list_prompts_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::list_prompts_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3296,7 +3370,9 @@ pub struct ListPromptsResult { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourceTemplatesRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::list_resource_templates_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::list_resource_templates_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3427,7 +3503,9 @@ pub struct ListResourceTemplatesResult { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListResourcesRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::list_resources_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::list_resources_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3570,7 +3648,9 @@ structure or access specific locations that the client has permission to read fr #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListRootsRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::list_roots_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::list_roots_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3728,7 +3808,9 @@ pub struct ListRootsResult { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ListToolsRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::list_tools_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::list_tools_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -3921,7 +4003,9 @@ impl ::std::fmt::Display for LoggingLevel { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct LoggingMessageNotification { + #[serde(deserialize_with = "validate::logging_message_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::logging_message_notification_method")] method: ::std::string::String, pub params: LoggingMessageNotificationParams, } @@ -4258,6 +4342,7 @@ impl ::std::fmt::Display for NumberSchemaType { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PaginatedRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::paginated_request_jsonrpc")] jsonrpc: ::std::string::String, pub method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] @@ -4379,7 +4464,9 @@ pub struct PaginatedResult { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PingRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::ping_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::ping_request_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -4566,7 +4653,9 @@ impl ::std::convert::From for PrimitiveSchemaDefinition { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ProgressNotification { + #[serde(deserialize_with = "validate::progress_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::progress_notification_method")] method: ::std::string::String, pub params: ProgressNotificationParams, } @@ -4822,7 +4911,9 @@ pub struct PromptArgument { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct PromptListChangedNotification { + #[serde(deserialize_with = "validate::prompt_list_changed_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::prompt_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -4941,7 +5032,7 @@ pub struct PromptReference { if present).*/ #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::prompt_reference_type_")] type_: ::std::string::String, } impl PromptReference { @@ -5005,7 +5096,9 @@ impl PromptReference { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ReadResourceRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::read_resource_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::read_resource_request_method")] method: ::std::string::String, pub params: ReadResourceRequestParams, } @@ -5490,7 +5583,7 @@ pub struct ResourceLink { if present).*/ #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::resource_link_type_")] type_: ::std::string::String, ///The URI of this resource. pub uri: ::std::string::String, @@ -5566,7 +5659,9 @@ impl ResourceLink { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceListChangedNotification { + #[serde(deserialize_with = "validate::resource_list_changed_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::resource_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -5641,6 +5736,13 @@ pub struct ResourceListChangedNotificationParams { /// "description": "A description of what this template is for.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", /// "type": "string" /// }, +/// "icons": { +/// "description": "Optional set of sized icons that the client can display in a user interface.\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": "array", +/// "items": { +/// "$ref": "#/definitions/Icon" +/// } +/// }, /// "mimeType": { /// "description": "The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.", /// "type": "string" @@ -5671,6 +5773,15 @@ pub struct ResourceTemplate { This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.*/ #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub description: ::std::option::Option<::std::string::String>, + /**Optional set of sized icons that the client can display in a user interface. + 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)*/ + #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] + pub icons: ::std::vec::Vec, ///See [General fields: _meta](/specification/draft/basic/index#meta) for notes on _meta usage. #[serde(rename = "_meta", default, skip_serializing_if = "::std::option::Option::is_none")] pub meta: ::std::option::Option<::serde_json::Map<::std::string::String, ::serde_json::Value>>, @@ -5718,7 +5829,7 @@ pub struct ResourceTemplate { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceTemplateReference { - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::resource_template_reference_type_")] type_: ::std::string::String, ///The URI or URI template of the resource. pub uri: ::std::string::String, @@ -5778,7 +5889,9 @@ impl ResourceTemplateReference { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ResourceUpdatedNotification { + #[serde(deserialize_with = "validate::resource_updated_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::resource_updated_notification_method")] method: ::std::string::String, pub params: ResourceUpdatedNotificationParams, } @@ -5966,7 +6079,9 @@ The server should then request an updated list of roots using the ListRootsReque /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct RootsListChangedNotification { + #[serde(deserialize_with = "validate::roots_list_changed_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::roots_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -6396,6 +6511,7 @@ impl ::std::convert::From for ServerNotification { /// #[derive(::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerRequest { PingRequest(PingRequest), CreateMessageRequest(CreateMessageRequest), @@ -6465,6 +6581,7 @@ impl ::std::convert::From for ServerRequest { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum ServerResult { InitializeResult(InitializeResult), ListResourcesResult(ListResourcesResult), @@ -6572,7 +6689,9 @@ impl ::std::convert::From for ServerResult { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SetLevelRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::set_level_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::set_level_request_method")] method: ::std::string::String, pub params: SetLevelRequestParams, } @@ -6676,7 +6795,7 @@ pub struct StringSchema { pub min_length: ::std::option::Option, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub title: ::std::option::Option<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::string_schema_type_")] type_: ::std::string::String, } impl StringSchema { @@ -6788,7 +6907,9 @@ impl ::std::fmt::Display for StringSchemaFormat { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct SubscribeRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::subscribe_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::subscribe_request_method")] method: ::std::string::String, pub params: SubscribeRequestParams, } @@ -6880,7 +7001,7 @@ pub struct TextContent { pub meta: ::std::option::Option<::serde_json::Map<::std::string::String, ::serde_json::Value>>, ///The text content of the message. pub text: ::std::string::String, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::text_content_type_")] type_: ::std::string::String, } impl TextContent { @@ -7102,7 +7223,7 @@ received from untrusted servers.*/ /// "type": "boolean" /// }, /// "idempotentHint": { -/// "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on the its environment.\n\n(This property is meaningful only when readOnlyHint == false)\n\nDefault: false", +/// "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on its environment.\n\n(This property is meaningful only when readOnlyHint == false)\n\nDefault: false", /// "type": "boolean" /// }, /// "openWorldHint": { @@ -7130,7 +7251,7 @@ pub struct ToolAnnotations { #[serde(rename = "destructiveHint", default, skip_serializing_if = "::std::option::Option::is_none")] pub destructive_hint: ::std::option::Option, /**If true, calling the tool repeatedly with the same arguments - will have no additional effect on the its environment. + will have no additional effect on its environment. (This property is meaningful only when readOnlyHint == false) Default: false*/ #[serde(rename = "idempotentHint", default, skip_serializing_if = "::std::option::Option::is_none")] @@ -7191,7 +7312,7 @@ pub struct ToolInputSchema { >, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::tool_input_schema_type_")] type_: ::std::string::String, } impl ToolInputSchema { @@ -7255,7 +7376,9 @@ impl ToolInputSchema { /// #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct ToolListChangedNotification { + #[serde(deserialize_with = "validate::tool_list_changed_notification_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::tool_list_changed_notification_method")] method: ::std::string::String, #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub params: ::std::option::Option, @@ -7346,7 +7469,7 @@ pub struct ToolOutputSchema { >, #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")] pub required: ::std::vec::Vec<::std::string::String>, - #[serde(rename = "type")] + #[serde(rename = "type", deserialize_with = "validate::tool_output_schema_type_")] type_: ::std::string::String, } impl ToolOutputSchema { @@ -7418,7 +7541,9 @@ impl ToolOutputSchema { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct UnsubscribeRequest { pub id: RequestId, + #[serde(deserialize_with = "validate::unsubscribe_request_jsonrpc")] jsonrpc: ::std::string::String, + #[serde(deserialize_with = "validate::unsubscribe_request_method")] method: ::std::string::String, pub params: UnsubscribeRequestParams, } diff --git a/src/generated_schema/draft/schema_utils.rs b/src/generated_schema/draft/schema_utils.rs index 2e79e35..08ce1bf 100644 --- a/src/generated_schema/draft/schema_utils.rs +++ b/src/generated_schema/draft/schema_utils.rs @@ -884,6 +884,7 @@ impl Display for ServerMessage { /// "Similar to JsonrpcRequest , but with the variants restricted to client-side requests." #[derive(Clone, Debug)] +#[allow(clippy::large_enum_variant)] pub struct ServerJsonrpcRequest { pub id: RequestId, jsonrpc: ::std::string::String, diff --git a/src/generated_schema/draft/validators.rs b/src/generated_schema/draft/validators.rs new file mode 100644 index 0000000..7ce976a --- /dev/null +++ b/src/generated_schema/draft/validators.rs @@ -0,0 +1,240 @@ +/// Validates that a deserialized string field matches a given constant value. +/// +/// This function is intended for use with `#[serde(deserialize_with)]` to enforce +/// that a field in a struct always has a fixed, expected string value during deserialization. +/// +/// # Parameters +/// - `struct_name`: The name of the struct where this validation is applied. +/// - `field_name`: The name of the field being validated. +/// - `expected`: The expected constant string value for the field. +/// - `deserializer`: The Serde deserializer for the field. +/// +/// # Returns +/// - `Ok(String)` if the deserialized value matches the expected value. +/// - `Err(D::Error)` if the value differs, with an error message indicating +/// which struct and field failed validation. +/// +pub fn const_str_validator<'de, D>( + struct_name: &'static str, + field_name: &'static str, + expected: &'static str, + deserializer: D, +) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let value: String = serde::Deserialize::deserialize(deserializer)?; + if value == expected { + Ok(value) + } else { + Err(serde::de::Error::custom(format!( + "Expected field `{field_name}` in struct `{struct_name}` as const value '{expected}', but got '{value}'", + ))) + } +} + +/// Macro to generate a field-specific validator function for use with Serde. +/// +/// This avoids repetitive boilerplate when you have multiple fields/structs +/// requiring constant string validation. +/// +/// # Syntax +/// ```ignore +/// validate!(fn_name, "StructName", "field_name", "expected_value"); +/// ``` +/// +/// - `fn_name`: The function name to generate. +/// - `StructName`: The name of the struct (for error messages). +/// - `field_name`: The name of the field (for error messages). +/// - `expected_value`: The required constant string value. +/// +macro_rules! validate { + ($func_name:ident, $struct:expr, $field:expr, $expected:expr $(,)?) => { + pub(crate) fn $func_name<'de, D>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + const_str_validator($struct, $field, $expected, deserializer) + } + }; +} + +//* Validator Functions *// +validate!(audio_content_type_, "AudioContent", "type_", "audio"); +validate!(boolean_schema_type_, "BooleanSchema", "type_", "boolean"); +validate!(call_tool_request_jsonrpc, "CallToolRequest", "jsonrpc", "2.0"); +validate!(call_tool_request_method, "CallToolRequest", "method", "tools/call"); +validate!(cancelled_notification_jsonrpc, "CancelledNotification", "jsonrpc", "2.0"); +validate!( + cancelled_notification_method, + "CancelledNotification", + "method", + "notifications/cancelled" +); +validate!(complete_request_jsonrpc, "CompleteRequest", "jsonrpc", "2.0"); +validate!(complete_request_method, "CompleteRequest", "method", "completion/complete"); +validate!(create_message_request_jsonrpc, "CreateMessageRequest", "jsonrpc", "2.0"); +validate!( + create_message_request_method, + "CreateMessageRequest", + "method", + "sampling/createMessage" +); +validate!(elicit_request_jsonrpc, "ElicitRequest", "jsonrpc", "2.0"); +validate!(elicit_request_method, "ElicitRequest", "method", "elicitation/create"); +validate!(elicit_requested_schema_type_, "ElicitRequestedSchema", "type_", "object"); +validate!(embedded_resource_type_, "EmbeddedResource", "type_", "resource"); +validate!(enum_schema_type_, "EnumSchema", "type_", "string"); +validate!(get_prompt_request_jsonrpc, "GetPromptRequest", "jsonrpc", "2.0"); +validate!(get_prompt_request_method, "GetPromptRequest", "method", "prompts/get"); +validate!(image_content_type_, "ImageContent", "type_", "image"); +validate!(initialize_request_jsonrpc, "InitializeRequest", "jsonrpc", "2.0"); +validate!(initialize_request_method, "InitializeRequest", "method", "initialize"); +validate!(initialized_notification_jsonrpc, "InitializedNotification", "jsonrpc", "2.0"); +validate!( + initialized_notification_method, + "InitializedNotification", + "method", + "notifications/initialized" +); +validate!(jsonrpc_error_jsonrpc, "JsonrpcError", "jsonrpc", "2.0"); +validate!(jsonrpc_notification_jsonrpc, "JsonrpcNotification", "jsonrpc", "2.0"); +validate!(jsonrpc_request_jsonrpc, "JsonrpcRequest", "jsonrpc", "2.0"); +validate!(jsonrpc_response_jsonrpc, "JsonrpcResponse", "jsonrpc", "2.0"); +validate!(list_prompts_request_jsonrpc, "ListPromptsRequest", "jsonrpc", "2.0"); +validate!(list_prompts_request_method, "ListPromptsRequest", "method", "prompts/list"); +validate!( + list_resource_templates_request_jsonrpc, + "ListResourceTemplatesRequest", + "jsonrpc", + "2.0" +); +validate!( + list_resource_templates_request_method, + "ListResourceTemplatesRequest", + "method", + "resources/templates/list" +); +validate!(list_resources_request_jsonrpc, "ListResourcesRequest", "jsonrpc", "2.0"); +validate!( + list_resources_request_method, + "ListResourcesRequest", + "method", + "resources/list" +); +validate!(list_roots_request_jsonrpc, "ListRootsRequest", "jsonrpc", "2.0"); +validate!(list_roots_request_method, "ListRootsRequest", "method", "roots/list"); +validate!(list_tools_request_jsonrpc, "ListToolsRequest", "jsonrpc", "2.0"); +validate!(list_tools_request_method, "ListToolsRequest", "method", "tools/list"); +validate!( + logging_message_notification_jsonrpc, + "LoggingMessageNotification", + "jsonrpc", + "2.0" +); +validate!( + logging_message_notification_method, + "LoggingMessageNotification", + "method", + "notifications/message" +); +validate!(paginated_request_jsonrpc, "PaginatedRequest", "jsonrpc", "2.0"); +validate!(ping_request_jsonrpc, "PingRequest", "jsonrpc", "2.0"); +validate!(ping_request_method, "PingRequest", "method", "ping"); +validate!(progress_notification_jsonrpc, "ProgressNotification", "jsonrpc", "2.0"); +validate!( + progress_notification_method, + "ProgressNotification", + "method", + "notifications/progress" +); +validate!( + prompt_list_changed_notification_jsonrpc, + "PromptListChangedNotification", + "jsonrpc", + "2.0" +); +validate!( + prompt_list_changed_notification_method, + "PromptListChangedNotification", + "method", + "notifications/prompts/list_changed" +); +validate!(prompt_reference_type_, "PromptReference", "type_", "ref/prompt"); +validate!(read_resource_request_jsonrpc, "ReadResourceRequest", "jsonrpc", "2.0"); +validate!( + read_resource_request_method, + "ReadResourceRequest", + "method", + "resources/read" +); +validate!(resource_link_type_, "ResourceLink", "type_", "resource_link"); +validate!( + resource_list_changed_notification_jsonrpc, + "ResourceListChangedNotification", + "jsonrpc", + "2.0" +); +validate!( + resource_list_changed_notification_method, + "ResourceListChangedNotification", + "method", + "notifications/resources/list_changed" +); +validate!( + resource_template_reference_type_, + "ResourceTemplateReference", + "type_", + "ref/resource" +); +validate!( + resource_updated_notification_jsonrpc, + "ResourceUpdatedNotification", + "jsonrpc", + "2.0" +); +validate!( + resource_updated_notification_method, + "ResourceUpdatedNotification", + "method", + "notifications/resources/updated" +); +validate!( + roots_list_changed_notification_jsonrpc, + "RootsListChangedNotification", + "jsonrpc", + "2.0" +); +validate!( + roots_list_changed_notification_method, + "RootsListChangedNotification", + "method", + "notifications/roots/list_changed" +); +validate!(set_level_request_jsonrpc, "SetLevelRequest", "jsonrpc", "2.0"); +validate!(set_level_request_method, "SetLevelRequest", "method", "logging/setLevel"); +validate!(string_schema_type_, "StringSchema", "type_", "string"); +validate!(subscribe_request_jsonrpc, "SubscribeRequest", "jsonrpc", "2.0"); +validate!(subscribe_request_method, "SubscribeRequest", "method", "resources/subscribe"); +validate!(text_content_type_, "TextContent", "type_", "text"); +validate!(tool_input_schema_type_, "ToolInputSchema", "type_", "object"); +validate!( + tool_list_changed_notification_jsonrpc, + "ToolListChangedNotification", + "jsonrpc", + "2.0" +); +validate!( + tool_list_changed_notification_method, + "ToolListChangedNotification", + "method", + "notifications/tools/list_changed" +); +validate!(tool_output_schema_type_, "ToolOutputSchema", "type_", "object"); +validate!(unsubscribe_request_jsonrpc, "UnsubscribeRequest", "jsonrpc", "2.0"); +validate!( + unsubscribe_request_method, + "UnsubscribeRequest", + "method", + "resources/unsubscribe" +); diff --git a/tests/common/common.rs b/tests/common/common.rs index 49fe706..4ba5b93 100644 --- a/tests/common/common.rs +++ b/tests/common/common.rs @@ -63,5 +63,6 @@ where ::Err: std::fmt::Debug, { let message_str = get_test_payload(test_payload_key).replace("PROTOCOL_VERSION", version); + //{"id":13,"jsonrpc":"2.0","method":"tools/list","params":{}} T::from_str(&message_str).unwrap() } diff --git a/tests/serde_smoke_test.rs b/tests/serde_smoke_test.rs new file mode 100644 index 0000000..5eb9632 --- /dev/null +++ b/tests/serde_smoke_test.rs @@ -0,0 +1,1092 @@ +mod test_deserialize { + + #[cfg(feature = "2024_11_05")] + use rust_mcp_schema::mcp_2024_11_05::*; + + #[cfg(feature = "2025_03_26")] + use rust_mcp_schema::mcp_2025_03_26::*; + + #[cfg(feature = "draft")] + use rust_mcp_schema::mcp_draft::*; + + #[cfg(any(feature = "latest", feature = "2025_06_18"))] + use rust_mcp_schema::*; + + use serde::{Deserialize, Serialize}; + use serde_json::json; + use serde_json::{Map, Value}; + use std::collections::HashMap; + + /* ---------------------- TESTS ---------------------- */ + + // Helper to test serialization and deserialization + fn test_serde(original: &T) + where + T: Serialize + for<'de> Deserialize<'de> + std::fmt::Debug, + { + // Serialize the original object to JSON + let json = serde_json::to_string(original).expect("Failed to serialize original object"); + + // Deserialize back to the same type + let deserialized: T = serde_json::from_str(&json).expect("Failed to deserialize JSON"); + + // Serialize the deserialized object to JSON + let json_deserialized = serde_json::to_string(&deserialized).expect("Failed to serialize deserialized object"); + + // Compare the JSON strings to ensure consistency + assert_eq!(json, json_deserialized, "JSON serialization mismatch for {original:?}"); + } + + #[cfg(not(feature = "2024_11_05"))] + #[test] + fn test_annotations() { + let ann = Annotations { + audience: vec![Role::User], + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + last_modified: Some("2025-01-01T00:00:00Z".to_string()), + priority: Some(0.5), + }; + test_serde(&ann); + + // Edge case: empty + let ann_empty = Annotations::default(); + test_serde(&ann_empty); + } + + #[cfg(not(feature = "2024_11_05"))] + #[test] + fn test_audio_content() { + let audio = AudioContent::new( + "YmFzZTY0ZGF0YQ==".to_string(), + "audio/mpeg".to_string(), + Some(Annotations::default()), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + Some(Map::new()), + ); + + test_serde(&audio); + + // Edge case: minimal + let audio_min = AudioContent::new( + "YmFzZTY0ZGF0YQ==".to_string(), + "audio/mpeg".to_string(), + None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + ); + test_serde(&audio_min); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_base_metadata() { + let meta = BaseMetadata { + name: "test_name".to_string(), + title: Some("Test Title".to_string()), + }; + test_serde(&meta); + + // Edge case: minimal + let meta_min = BaseMetadata { + name: "test".to_string(), + title: None, + }; + test_serde(&meta_min); + } + + #[test] + fn test_blob_resource_contents() { + let blob = BlobResourceContents { + blob: "YmFzZTY0YmxvYg==".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: Some(Map::new()), + mime_type: Some("application/octet-stream".to_string()), + uri: "https://example.com/blob".to_string(), + }; + test_serde(&blob); + + // Edge case: minimal + let blob_min = BlobResourceContents { + blob: "YmFzZTY0YmxvYg==".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + mime_type: None, + uri: "https://example.com/blob".to_string(), + }; + test_serde(&blob_min); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_boolean_schema() { + let default = Some(true); + let description = Some("Test boolean".to_string()); + let title = Some("Bool Title".to_string()); + + let schema = BooleanSchema::new(default, description, title); + test_serde(&schema); + + // Edge case: minimal + let schema_min = BooleanSchema::new(None, None, None); + test_serde(&schema_min); + } + + #[cfg(not(feature = "draft"))] + #[test] + fn test_call_tool_request() { + let params = CallToolRequestParams { + arguments: Some(Map::new()), + name: "test_tool".to_string(), + }; + let req = CallToolRequest::new(params); + test_serde(&req); + + // Validation error test + let invalid_json = json!({ + "method": "wrong/method", + "params": { + "arguments": {}, + "name": "test_tool" + } + }); + let result: std::result::Result = serde_json::from_value(invalid_json); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Expected field `method` in struct `CallToolRequest` as const value 'tools/call'")); + } + + #[test] + fn test_call_tool_result() { + let result = CallToolResult { + content: vec![], + is_error: Some(false), + meta: Some(Map::new()), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + structured_content: Some(Map::new()), + }; + test_serde(&result); + + // Edge case: minimal + let result_min = CallToolResult { + content: vec![], + is_error: None, + meta: None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + structured_content: None, + }; + test_serde(&result_min); + } + + #[test] + fn test_cancelled_notification() { + let params = CancelledNotificationParams { + reason: Some("test reason".to_string()), + request_id: RequestId::Integer(1), + }; + let notif = CancelledNotification::new(params); + test_serde(¬if); + + // Validation error test + let invalid_json = json!({ + "method": "wrong/method", + "params": { + "requestId": 1, + "reason": "test" + } + }); + let result: std::result::Result = serde_json::from_value(invalid_json); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Expected field `method` in struct `CancelledNotification` as const value 'notifications/cancelled'")); + } + + #[test] + fn test_client_capabilities() { + let capabilities = ClientCapabilities { + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + elicitation: Some(Map::new()), + experimental: Some(HashMap::new()), + roots: Some(ClientCapabilitiesRoots::default()), + sampling: Some(Map::new()), + }; + test_serde(&capabilities); + + // Edge case: empty + let cap_empty = ClientCapabilities::default(); + test_serde(&cap_empty); + } + + #[test] + fn test_client_capabilities_roots() { + let roots = ClientCapabilitiesRoots { + list_changed: Some(true), + }; + test_serde(&roots); + + // Edge case: empty + let roots_empty = ClientCapabilitiesRoots::default(); + test_serde(&roots_empty); + } + + #[test] + fn test_client_notification() { + let notif = ClientNotification::CancelledNotification(CancelledNotification::new(CancelledNotificationParams { + reason: None, + request_id: RequestId::Integer(0), + })); + test_serde(¬if); + } + + #[cfg(not(feature = "draft"))] + #[test] + fn test_client_request() { + let req = ClientRequest::CallToolRequest(CallToolRequest::new(CallToolRequestParams { + arguments: None, + name: "name".to_string(), + })); + test_serde(&req); + } + + #[test] + fn test_client_result() { + let result = ClientResult::CreateMessageResult(CreateMessageResult { + content: CreateMessageResultContent::TextContent(TextContent::new( + "test".to_string(), + None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + )), + meta: None, + model: "model".to_string(), + role: Role::Assistant, + stop_reason: None, + }); + test_serde(&result); + } + + #[cfg(not(feature = "draft"))] + #[test] + fn test_complete_request() { + let argument = CompleteRequestParamsArgument { + name: "test".to_string(), + value: "test".to_string(), + }; + + let params = CompleteRequestParams { + argument, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + context: None, + ref_: CompleteRequestParamsRef::PromptReference(PromptReference::new( + "test".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + )), + }; + + let req = CompleteRequest::new(params); + test_serde(&req); + } + + #[test] + fn test_complete_result() { + let completion = CompleteResultCompletion { + has_more: Some(false), + total: Some(0), + values: vec![], + }; + + let result = CompleteResult { completion, meta: None }; + test_serde(&result); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_content_block() { + let block = ContentBlock::TextContent(TextContent::new( + "test".to_string(), + None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + )); + test_serde(&block); + } + + #[test] + fn test_create_message_request() { + let params = CreateMessageRequestParams { + include_context: None, + max_tokens: 100, + messages: vec![], + metadata: None, + model_preferences: None, + stop_sequences: vec![], + system_prompt: None, + temperature: None, + }; + + let req = CreateMessageRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + params, + ); + test_serde(&req); + } + + #[test] + fn test_create_message_result() { + let result = CreateMessageResult { + content: CreateMessageResultContent::TextContent(TextContent::new( + "test".to_string(), + None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + )), + meta: None, + model: "model".to_string(), + role: Role::Assistant, + stop_reason: None, + }; + test_serde(&result); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_elicit_request() { + let params = ElicitRequestParams { + message: "test".to_string(), + requested_schema: ElicitRequestedSchema::new(HashMap::new(), vec![]), + }; + + let req = ElicitRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + params, + ); + test_serde(&req); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_elicit_result() { + let result = ElicitResult { + meta: None, + action: ElicitResultAction::Accept, + content: None, + }; + test_serde(&result); + } + + #[test] + fn test_embedded_resource() { + let resource = EmbeddedResource::new( + EmbeddedResourceResource::TextResourceContents(TextResourceContents { + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + mime_type: None, + text: "test".to_string(), + uri: "ice://test".to_string(), + }), + None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + ); + + test_serde(&resource); + } + + #[test] + fn test_get_prompt_request() { + let params = GetPromptRequestParams { + name: "test".to_string(), + arguments: None, + }; + + let req = GetPromptRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + params, + ); + test_serde(&req); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_get_prompt_result() { + let block = ContentBlock::TextContent(TextContent::new("test".to_string(), None, None)); + + let result = GetPromptResult { + meta: None, + description: None, + messages: vec![PromptMessage { + content: block, + role: Role::Assistant, + }], + }; + test_serde(&result); + } + + #[test] + fn test_image_content() { + let annotations = None; + let data = "base64".to_string(); + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + let meta = None; + let mime_type = "image/png".to_string(); + + let content = ImageContent::new( + data, + mime_type, + annotations, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta, + ); + + test_serde(&content); + } + + #[test] + fn test_initialize_request() { + let params = InitializeRequestParams { + capabilities: ClientCapabilities { + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + elicitation: None, + experimental: None, + roots: None, + sampling: None, + }, + protocol_version: "2025-06-18".to_string(), + client_info: Implementation { + name: "name".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: Some("title".to_string()), + version: "version".to_string(), + #[cfg(feature = "draft")] + icons: vec![], + #[cfg(feature = "draft")] + website_url: Some("https://github.com/rust-mcp-stack/rust-mcp-sdk".to_string()), + }, + }; + + let req = InitializeRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + params, + ); + test_serde(&req); + } + + #[test] + fn test_initialize_result() { + let result = InitializeResult { + capabilities: ServerCapabilities::default(), + meta: None, + protocol_version: "2025-06-18".to_string(), + instructions: None, + server_info: Implementation { + name: "name".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: Some("title".to_string()), + version: "version".to_string(), + #[cfg(feature = "draft")] + icons: vec![], + #[cfg(feature = "draft")] + website_url: Some("https://github.com/rust-mcp-stack/rust-mcp-sdk".to_string()), + }, + }; + test_serde(&result); + } + + #[test] + fn test_initialized_notification() { + let params = InitializedNotificationParams { meta: None, extra: None }; + + let notif = InitializedNotification::new(Some(params)); + test_serde(¬if); + } + + #[test] + fn test_logging_message_notification() { + let params = LoggingMessageNotificationParams { + level: LoggingLevel::Info, + data: Value::String("data".to_string()), + logger: Some("fogger".to_string()), + }; + + let notif = LoggingMessageNotification::new(params); + test_serde(¬if); + } + + #[test] + fn test_list_prompts_request() { + let params = ListPromptsRequestParams { cursor: None }; + + let req = ListPromptsRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + Some(params), + ); + test_serde(&req); + } + + #[test] + fn test_list_prompts_result() { + let result = ListPromptsResult { + prompts: vec![], + meta: None, + next_cursor: None, + }; + test_serde(&result); + } + + #[test] + fn test_list_resources_request() { + let params = ListResourcesRequestParams { cursor: None }; + + let req = ListResourcesRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + Some(params), + ); + test_serde(&req); + } + + #[test] + fn test_list_resources_result() { + let result = ListResourcesResult { + resources: vec![], + meta: None, + next_cursor: None, + }; + test_serde(&result); + } + + #[test] + fn test_list_resource_templates_request() { + let params = ListResourceTemplatesRequestParams { cursor: None }; + + let req = ListResourceTemplatesRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + Some(params), + ); + test_serde(&req); + } + + #[test] + fn test_list_resource_templates_result() { + let result = ListResourceTemplatesResult { + meta: None, + next_cursor: None, + resource_templates: vec![ResourceTemplate { + annotations: None, + description: None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + mime_type: None, + name: "name".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: None, + uri_template: "ice://something".to_string(), + #[cfg(feature = "draft")] + icons: vec![], + }], + }; + test_serde(&result); + } + + #[test] + fn test_list_roots_request() { + let req = ListRootsRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + Some(ListRootsRequestParams { meta: None, extra: None }), + ); + test_serde(&req); + } + + #[test] + fn test_list_roots_result() { + let result = ListRootsResult { + roots: vec![], + meta: None, + }; + test_serde(&result); + } + + #[test] + fn test_list_tools_request() { + let params = ListToolsRequestParams { cursor: None }; + let req = ListToolsRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + Some(params), + ); + test_serde(&req); + } + + #[test] + fn test_list_tools_result() { + let result = ListToolsResult { + tools: vec![], + meta: None, + next_cursor: None, + }; + test_serde(&result); + } + + #[test] + fn test_model_preferences() { + let pref = ModelPreferences { + cost_priority: None, + hints: vec![], + intelligence_priority: None, + speed_priority: None, + }; + test_serde(&pref); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_number_schema() { + let schema = NumberSchema { + description: None, + maximum: None, + minimum: None, + title: None, + type_: NumberSchemaType::Integer, + #[cfg(feature = "draft")] + default: None, + }; + test_serde(&schema); + } + + #[test] + fn test_ping_request() { + let params = PingRequestParams { meta: None, extra: None }; + let req = PingRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + Some(params), + ); + test_serde(&req); + } + + #[test] + fn test_progress_notification() { + let params = ProgressNotificationParams { + progress: 52., + #[cfg(not(feature = "2024_11_05"))] + message: None, + progress_token: ProgressToken::Integer(15), + total: None, + }; + + let notif = ProgressNotification::new(params); + test_serde(¬if); + } + + #[test] + fn test_prompt() { + let prompt = Prompt { + description: None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + name: "test".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: None, + arguments: vec![PromptArgument { + description: None, + name: "name".to_string(), + required: None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: None, + }], + #[cfg(feature = "draft")] + icons: vec![], + }; + test_serde(&prompt); + } + + #[test] + fn test_prompt_list_changed_notification() { + let params = PromptListChangedNotificationParams { meta: None, extra: None }; + let notif = PromptListChangedNotification::new(Some(params)); + test_serde(¬if); + } + + #[test] + fn test_prompt_reference() { + let ref_ = PromptReference::new( + "name".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + Some("title".to_string()), + ); + + test_serde(&ref_); + } + + #[test] + fn test_read_resource_request() { + let params = ReadResourceRequestParams { uri: "test".to_string() }; + let req = ReadResourceRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + params, + ); + test_serde(&req); + } + + #[test] + fn test_read_resource_result() { + let result = ReadResourceResult { + meta: None, + contents: vec![ReadResourceResultContentsItem::TextResourceContents(TextResourceContents { + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + mime_type: None, + text: "test".to_string(), + uri: "ice://test".to_string(), + })], + }; + test_serde(&result); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_resource_link() { + let annotations = None; + let meta = None; + let uri = "test".to_string(); + let description = None; + let mime_type = None; + let name = "name".to_string(); + let size = None; + let title = None; + let link = ResourceLink::new( + #[cfg(feature = "draft")] + vec![], + name, + uri, + annotations, + description, + meta, + mime_type, + size, + title, + ); + test_serde(&link); + } + + #[test] + fn test_resource_list_changed_notification() { + let params = ResourceListChangedNotificationParams { meta: None, extra: None }; + let notif = ResourceListChangedNotification::new(Some(params)); + test_serde(¬if); + } + + #[test] + fn test_resource_template() { + let template = ResourceTemplate { + annotations: None, + description: None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + name: "test".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: None, + mime_type: Some("mime/pine".to_string()), + uri_template: "ice://something".to_string(), + #[cfg(feature = "draft")] + icons: vec![], + }; + test_serde(&template); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_resource_template_reference() { + let ref_ = ResourceTemplateReference::new("ice://something".to_string()); + test_serde(&ref_); + } + + #[test] + fn test_resource_updated_notification() { + let params = ResourceUpdatedNotificationParams { + uri: "ice://something".to_string(), + }; + let notif = ResourceUpdatedNotification::new(params); + test_serde(¬if); + } + + #[test] + fn test_result() { + let result = Result { meta: None, extra: None }; + test_serde(&result); + } + + #[test] + fn test_role() { + let role = Role::User; + test_serde(&role); + } + + #[test] + fn test_sampling_message() { + let content = TextContent::new( + "SamplingMessageContent".to_string(), + None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + ); + + let message = SamplingMessage { + content: SamplingMessageContent::TextContent(content), + role: Role::User, + }; + test_serde(&message); + } + + #[test] + fn test_server_capabilities() { + let cap = ServerCapabilities::default(); + test_serde(&cap); + } + + #[test] + fn test_server_capabilities_tools() { + let tools = ServerCapabilitiesTools { + list_changed: Some(true), + }; + test_serde(&tools); + } + + #[test] + fn test_server_notification() { + let notif = ServerNotification::CancelledNotification(CancelledNotification::new(CancelledNotificationParams { + reason: Some("because".to_string()), + request_id: RequestId::Integer(15), + })); + test_serde(¬if); + } + + #[test] + fn test_server_request() { + let req = ServerRequest::PingRequest(PingRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(97), + None, + )); + test_serde(&req); + } + + #[test] + fn test_server_result() { + let result = ServerResult::InitializeResult(InitializeResult { + capabilities: ServerCapabilities::default(), + meta: None, + protocol_version: "2025-06-18".to_string(), + instructions: None, + server_info: Implementation { + name: "name".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: Some("title".to_string()), + version: "version".to_string(), + #[cfg(feature = "draft")] + icons: vec![], + #[cfg(feature = "draft")] + website_url: Some("https://github.com/rust-mcp-stack/rust-mcp-sdk".to_string()), + }, + }); + test_serde(&result); + } + + #[test] + fn test_set_level_request() { + let params = SetLevelRequestParams { + level: LoggingLevel::Info, + }; + + let req = SetLevelRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(23), + params, + ); + test_serde(&req); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_string_schema() { + let schema = StringSchema::new( + #[cfg(feature = "draft")] + Some("default".to_string()), + Some("description".to_string()), + None, + Some(21), + Some(15), + Some("title".to_string()), + ); + + test_serde(&schema); + } + + #[test] + fn test_subscribe_request() { + let params = SubscribeRequestParams { uri: "test".to_string() }; + + let req = SubscribeRequest::new( + #[cfg(feature = "draft")] + RequestId::Integer(22), + params, + ); + test_serde(&req); + } + + #[test] + fn test_text_content() { + let content = TextContent::new( + "test".to_string(), + None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + None, + ); + test_serde(&content); + } + + #[test] + fn test_text_resource_contents() { + let content = TextResourceContents { + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + mime_type: None, + text: "test".to_string(), + uri: "test".to_string(), + }; + test_serde(&content); + } + + #[test] + fn test_tool() { + let input_schema = ToolInputSchema::new(vec!["hey".to_string()], None); + let tool = Tool { + #[cfg(not(feature = "2024_11_05"))] + annotations: None, + description: None, + input_schema, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + meta: None, + name: "test".to_string(), + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + output_schema: None, + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + title: None, + #[cfg(feature = "draft")] + icons: vec![], + }; + test_serde(&tool); + } + + #[cfg(not(feature = "2024_11_05"))] + #[test] + fn test_tool_annotations() { + let ann = ToolAnnotations::default(); + test_serde(&ann); + } + + #[test] + fn test_tool_input_schema() { + let schema = ToolInputSchema::new(vec!["hey".to_string()], None); + test_serde(&schema); + } + + #[test] + fn test_tool_list_changed_notification() { + let notif = ToolListChangedNotification::new(None); + test_serde(¬if); + } + + #[cfg(any(feature = "draft", feature = "2025_06_18"))] + #[test] + fn test_tool_output_schema() { + let schema = ToolOutputSchema::new(vec!["hey".to_string()], None); + test_serde(&schema); + } + + #[cfg(not(feature = "draft"))] + #[test] + fn test_unsubscribe_request() { + let params = UnsubscribeRequestParams { uri: "test".to_string() }; + + let req = UnsubscribeRequest::new(params); + test_serde(&req); + } + + #[test] + fn test_rpc_error() { + let error = RpcError { + code: -32600, + data: None, + message: "test".to_string(), + }; + test_serde(&error); + } + + #[test] + fn test_client_request_deserialization() { + let json = json!({ + "method": "tools/call", + "params": { + "name": "add", + "arguments": {} + } + }); + + let req: ClientRequest = serde_json::from_value(json).unwrap(); + if let ClientRequest::CallToolRequest(req) = req { + assert_eq!(req.method(), "tools/call"); + assert_eq!(req.params.name, "add"); + } else { + panic!("Unexpected variant"); + } + } + + #[test] + fn test_server_request_deserialization() { + let json = json!({ + "method": "sampling/createMessage", + "params": { + "maxTokens": 100, + "messages": [] + } + }); + + let req: ServerRequest = serde_json::from_value(json).unwrap(); + if let ServerRequest::CreateMessageRequest(req) = req { + assert_eq!(req.method(), "sampling/createMessage"); + } else { + panic!("Unexpected variant"); + } + } + + #[test] + fn test_client_notification_deserialization() { + let json = json!({ + "method": "notifications/cancelled", + "params": { + "requestId": 1, + "reason": "test" + } + }); + + let notif: ClientNotification = serde_json::from_value(json).unwrap(); + if let ClientNotification::CancelledNotification(req) = notif { + assert_eq!(req.method(), "notifications/cancelled"); + } else { + panic!("Unexpected variant"); + } + } +} diff --git a/tests/test_deserialize.rs b/tests/test_deserialize.rs index 6cf6df3..093bc09 100644 --- a/tests/test_deserialize.rs +++ b/tests/test_deserialize.rs @@ -19,6 +19,7 @@ mod test_deserialize { use rust_mcp_schema::mcp_draft::*; #[cfg(any(feature = "latest", feature = "2025_06_18"))] use rust_mcp_schema::*; + use serde_json::json; use super::common::get_message; @@ -297,4 +298,39 @@ mod test_deserialize { let message: ServerMessage = get_message("err_sampling_rejected", LATEST_PROTOCOL_VERSION); assert!(matches!(message, ServerMessage::Error(_))); } + + #[test] + fn test_deserialize_with_wrong_method() { + let payload = r#"{"method":"sampling/INVALID","params":{"maxTokens":0,"messages":[]}}"#; + let result = serde_json::from_str::(payload); + assert!(result.is_err()); + } + + #[test] + fn test_deserialize_with_wrong_jsonrpc_version() { + let payload = r#"{"error":{"code":-32603,"message":"Internal error"},"id":0,"jsonrpc":"1.0"}"#; + + let result = serde_json::from_str::(payload); + assert!(result.is_err()); + } + + #[test] + fn test_server_notification_deserialization() { + let json = json!({ + "method": "notifications/progress", + "params": { + "progress": 50, + "status": "test", + "task_id": "test", + "progressToken":"xyz" + } + }); + + let notif: ServerNotification = serde_json::from_value(json).unwrap(); + if let ServerNotification::ProgressNotification(req) = notif { + assert_eq!(req.method(), "notifications/progress"); + } else { + panic!("Unexpected variant"); + } + } }