diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 6b3ec7e..8032fc0 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -42,7 +42,7 @@ body: id: version attributes: label: Version - description: Specify which version of rust-mcp-schema you're using (e.g., latest, draft, or a specific version number). + description: Specify which version of rust-mcp-schema you're using. render: text validations: required: false @@ -50,7 +50,7 @@ body: id: version attributes: label: MCP Schema Version - description: Specify which version of mcp schema specification you're using (e.g., latest, draft, 2024_11_05). + description: Specify which version of mcp schema specification you're using. render: text validations: required: false diff --git a/Cargo.toml b/Cargo.toml index 491be6a..30b64e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,10 +39,10 @@ features = ["latest", "schema_utils"] default = ["latest", "schema_utils"] # Default features # activates the latest MCP schema version, this will be updated once a new version of schema is published -latest = ["2024_11_05"] +latest = ["2025_03_26"] -# enables the draft version of the mcp schema -draft = [] +# enabled mcp schema version 2025_03_26 +2025_03_26 = [] # enabled mcp schema version 2024_11_05 2024_11_05 = [] # Enables `schema_utils`, which provides utility types that simplify communication with MCP messages, improving ease of use while reducing potential mistakes ane errors when constructing messages. diff --git a/README.md b/README.md index cf06297..98329c6 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,12 @@ [build status ](https://github.com/rust-mcp-stack/rust-mcp-schema/actions/workflows/ci.yml) -A type-safe implementation of the official Model Context Protocol (MCP) schema in Rust. +A type-safe implementation of the official Model Context Protocol (MCP) schema in Rust, supporting both the `2024_11_05` and `2025_03_26` versions. The MCP schemas in this repository are [automatically generated](#how-are-schemas-generated) from the official Model Context Protocol, ensuring they are always up-to-date and aligned with the latest official specifications. --- + **Note:** This crate **only** provides an implementation of the MCP schema. If you are looking for a high-performance, asynchronous toolkit for building MCP servers and clients, checkout [rust-mcp-sdk](https://crates.io/crates/rust-mcp-sdk). @@ -27,7 +28,6 @@ Focus on your app's logic while [rust-mcp-sdk](https://crates.io/crates/rust-mcp - [How can this crate be used?](#how-can-this-crate-be-used) - [Schema Versions](#schema-versions) - - [Currently available versions](#currently-available-versions) - [How to switch between different schema versions?](#how-to-switch-between-different-schema-versions) - [How are Schemas generated?](#how-are-schemas-generated) - [What is `schema_utils`?](#what-is-schema_utils) @@ -46,7 +46,7 @@ Focus on your app's logic while [rust-mcp-sdk](https://crates.io/crates/rust-mcp - 🧩 Type-safe implementation of the MCP protocol specification. - 💎 Auto-generated schemas are always synchronized with the official schema specifications. -- 📜 Includes all schema versions, including draft versions for early adoption. +- 📜 Includes both schema versions : `2024_11_05` and `2025_03_26`. - 🛠 Complimentary schema utility module (schema_utils) to boost productivity and ensure development integrity. ## How can this crate be used? @@ -68,25 +68,23 @@ For more information on the MCP architecture, refer to the [official documentati ## Schema Versions -This repository provides all versions of the schema, including draft versions, enabling you to prepare and adapt your applications ahead of upcoming official schema releases. - -### Currently available versions +This repository provides all versions of the schema, which can be selected using Cargo features: - [2024_11_05](src/generated_schema/2024_11_05) -- [Draft](src/generated_schema/draft) +- [2025_03_26](src/generated_schema/2025_03_26) ### How to switch between different schema versions? Each schema version has a corresponding Cargo feature that can be enabled in your project's Cargo.toml. -By default, the latest version of the schema is active. +By default, the version `2024_11_05` of the schema is active. -Example: enable `draft` version of the shema: +Example: enable `2025_03_26` version of the shema: ```toml # Cargo.toml -rust-mcp-schema = { version: 0.2.2 , features=["draft"] } +rust-mcp-schema = { version: 0.2.2 , features=["2025_03_26"] } ``` Example: enable `latest` version of the shema: diff --git a/scripts/run_clippy.sh b/scripts/run_clippy.sh index 00e45b9..a152f36 100755 --- a/scripts/run_clippy.sh +++ b/scripts/run_clippy.sh @@ -4,7 +4,7 @@ COMMON_FEATURES=("schema_utils") # schema versions features (passed to clippy one at a time) -SCHEMA_VERSION_FEATURES=("2024_11_05" "draft") +SCHEMA_VERSION_FEATURES=("2025_03_26", "2024_11_05") # space-separated string COMMON_FEATURES_STR="${COMMON_FEATURES[*]}" diff --git a/scripts/run_test.sh b/scripts/run_test.sh index 8e3f953..433841a 100755 --- a/scripts/run_test.sh +++ b/scripts/run_test.sh @@ -4,7 +4,7 @@ COMMON_FEATURES=("schema_utils") # schema versions features (tested one at a time) -SCHEMA_VERSION_FEATURES=("2024_11_05" "draft") +SCHEMA_VERSION_FEATURES=("2025_03_26", "2024_11_05") # space-separated string COMMON_FEATURES_STR="${COMMON_FEATURES[*]}" diff --git a/src/generated_schema.rs b/src/generated_schema.rs index 696e53b..77c8f5d 100644 --- a/src/generated_schema.rs +++ b/src/generated_schema.rs @@ -1,25 +1,25 @@ -/// Schema Version : Draft -#[cfg(feature = "draft")] -#[path = "generated_schema/draft/mcp_schema.rs"] -mod schema_draft; -#[cfg(feature = "draft")] -pub use schema_draft::*; - -#[cfg(all(feature = "schema_utils", feature = "draft"))] -#[path = "generated_schema/draft/schema_utils.rs"] -pub mod schema_utils; - /// Schema Version : 2024_11_05 #[cfg(feature = "2024_11_05")] -#[cfg(not(feature = "draft"))] #[path = "generated_schema/2024_11_05/mcp_schema.rs"] mod schema_2024_11_05; - #[cfg(feature = "2024_11_05")] -#[cfg(not(feature = "draft"))] pub use schema_2024_11_05::*; #[cfg(all(feature = "schema_utils", feature = "2024_11_05"))] -#[cfg(not(feature = "draft"))] #[path = "generated_schema/2024_11_05/schema_utils.rs"] pub mod schema_utils; + +/// Schema Version : 2025_03_26 +#[cfg(feature = "2025_03_26")] +#[cfg(not(feature = "2024_11_05"))] +#[path = "generated_schema/2025_03_26/mcp_schema.rs"] +mod schema_2025_03_26; + +#[cfg(feature = "2025_03_26")] +#[cfg(not(feature = "2024_11_05"))] +pub use schema_2025_03_26::*; + +#[cfg(all(feature = "schema_utils", feature = "2025_03_26"))] +#[cfg(not(feature = "2024_11_05"))] +#[path = "generated_schema/2025_03_26/schema_utils.rs"] +pub mod schema_utils; diff --git a/src/generated_schema/2024_11_05/mcp_schema.rs b/src/generated_schema/2024_11_05/mcp_schema.rs index 18a9c27..26aadb3 100644 --- a/src/generated_schema/2024_11_05/mcp_schema.rs +++ b/src/generated_schema/2024_11_05/mcp_schema.rs @@ -1,12 +1,12 @@ /// ---------------------------------------------------------------------------- -/// This file is auto-generated by mcp-schema-gen v0.1.15. +/// This file is auto-generated by mcp-schema-gen v0.1.16. /// 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 : eb4abdf2bb91e0d5afd94510741eadd416982350 -/// Generated at : 2025-03-23 16:07:00 +/// Hash : 72516795d9a7aacdcf9b87624feb05229e10c950 +/// Generated at : 2025-04-03 19:11:35 /// ---------------------------------------------------------------------------- /// /// MCP Protocol Version diff --git a/src/generated_schema/draft/mcp_schema.rs b/src/generated_schema/2025_03_26/mcp_schema.rs similarity index 95% rename from src/generated_schema/draft/mcp_schema.rs rename to src/generated_schema/2025_03_26/mcp_schema.rs index 627ffd7..87846fa 100644 --- a/src/generated_schema/draft/mcp_schema.rs +++ b/src/generated_schema/2025_03_26/mcp_schema.rs @@ -1,16 +1,16 @@ /// ---------------------------------------------------------------------------- -/// This file is auto-generated by mcp-schema-gen v0.1.15. +/// This file is auto-generated by mcp-schema-gen v0.1.16. /// 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 : eb4abdf2bb91e0d5afd94510741eadd416982350 -/// Generated at : 2025-03-23 16:07:01 +/// Hash : 72516795d9a7aacdcf9b87624feb05229e10c950 +/// Generated at : 2025-04-03 19:11:35 /// ---------------------------------------------------------------------------- /// /// MCP Protocol Version -pub const LATEST_PROTOCOL_VERSION: &str = "DRAFT-2025-v1"; +pub const LATEST_PROTOCOL_VERSION: &str = "2025-03-26"; /// JSON-RPC Version pub const JSONRPC_VERSION: &str = "2.0"; /// Parse error. Invalid JSON was received. An error occurred while parsing the JSON text. @@ -1913,6 +1913,120 @@ pub struct InitializedNotificationParams { #[serde(flatten, default, skip_serializing_if = "::std::option::Option::is_none")] pub extra: ::std::option::Option<::serde_json::Map<::std::string::String, ::serde_json::Value>>, } +///A JSON-RPC batch request, as described in +/// +///
JSON schema +/// +/// ```json +///{ +/// "description": "A JSON-RPC batch request, as described in ", +/// "type": "array", +/// "items": { +/// "anyOf": [ +/// { +/// "$ref": "#/definitions/JSONRPCRequest" +/// }, +/// { +/// "$ref": "#/definitions/JSONRPCNotification" +/// } +/// ] +/// } +///} +/// ``` +///
+#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] +#[serde(transparent)] +pub struct JsonrpcBatchRequest(pub ::std::vec::Vec); +///JsonrpcBatchRequestItem +/// +///
JSON schema +/// +/// ```json +///{ +/// "anyOf": [ +/// { +/// "$ref": "#/definitions/JSONRPCRequest" +/// }, +/// { +/// "$ref": "#/definitions/JSONRPCNotification" +/// } +/// ] +///} +/// ``` +///
+#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] +#[serde(untagged)] +pub enum JsonrpcBatchRequestItem { + Request(JsonrpcRequest), + Notification(JsonrpcNotification), +} +impl ::std::convert::From for JsonrpcBatchRequestItem { + fn from(value: JsonrpcRequest) -> Self { + Self::Request(value) + } +} +impl ::std::convert::From for JsonrpcBatchRequestItem { + fn from(value: JsonrpcNotification) -> Self { + Self::Notification(value) + } +} +///A JSON-RPC batch response, as described in +/// +///
JSON schema +/// +/// ```json +///{ +/// "description": "A JSON-RPC batch response, as described in ", +/// "type": "array", +/// "items": { +/// "anyOf": [ +/// { +/// "$ref": "#/definitions/JSONRPCResponse" +/// }, +/// { +/// "$ref": "#/definitions/JSONRPCError" +/// } +/// ] +/// } +///} +/// ``` +///
+#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] +#[serde(transparent)] +pub struct JsonrpcBatchResponse(pub ::std::vec::Vec); +///JsonrpcBatchResponseItem +/// +///
JSON schema +/// +/// ```json +///{ +/// "anyOf": [ +/// { +/// "$ref": "#/definitions/JSONRPCResponse" +/// }, +/// { +/// "$ref": "#/definitions/JSONRPCError" +/// } +/// ] +///} +/// ``` +///
+#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] +#[serde(untagged)] +pub enum JsonrpcBatchResponseItem { + Response(JsonrpcResponse), + Error(JsonrpcError), +} +impl ::std::convert::From for JsonrpcBatchResponseItem { + fn from(value: JsonrpcResponse) -> Self { + Self::Response(value) + } +} +impl ::std::convert::From for JsonrpcBatchResponseItem { + fn from(value: JsonrpcError) -> Self { + Self::Error(value) + } +} ///A response to a request that indicates an error occurred. /// ///
JSON schema @@ -2013,12 +2127,13 @@ pub struct JsonrpcErrorError { ///A short description of the error. The message SHOULD be limited to a concise single sentence. pub message: ::std::string::String, } -///JsonrpcMessage +///Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. /// ///
JSON schema /// /// ```json ///{ +/// "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.", /// "anyOf": [ /// { /// "$ref": "#/definitions/JSONRPCRequest" @@ -2027,10 +2142,16 @@ pub struct JsonrpcErrorError { /// "$ref": "#/definitions/JSONRPCNotification" /// }, /// { +/// "$ref": "#/definitions/JSONRPCBatchRequest" +/// }, +/// { /// "$ref": "#/definitions/JSONRPCResponse" /// }, /// { /// "$ref": "#/definitions/JSONRPCError" +/// }, +/// { +/// "$ref": "#/definitions/JSONRPCBatchResponse" /// } /// ] ///} @@ -2041,8 +2162,10 @@ pub struct JsonrpcErrorError { pub enum JsonrpcMessage { Request(JsonrpcRequest), Notification(JsonrpcNotification), + BatchRequest(JsonrpcBatchRequest), Response(JsonrpcResponse), Error(JsonrpcError), + BatchResponse(JsonrpcBatchResponse), } impl ::std::convert::From for JsonrpcMessage { fn from(value: JsonrpcRequest) -> Self { @@ -2054,6 +2177,11 @@ impl ::std::convert::From for JsonrpcMessage { Self::Notification(value) } } +impl ::std::convert::From for JsonrpcMessage { + fn from(value: JsonrpcBatchRequest) -> Self { + Self::BatchRequest(value) + } +} impl ::std::convert::From for JsonrpcMessage { fn from(value: JsonrpcResponse) -> Self { Self::Response(value) @@ -2064,6 +2192,11 @@ impl ::std::convert::From for JsonrpcMessage { Self::Error(value) } } +impl ::std::convert::From for JsonrpcMessage { + fn from(value: JsonrpcBatchResponse) -> Self { + Self::BatchResponse(value) + } +} ///A notification which does not expect a response. /// ///
JSON schema @@ -4721,6 +4854,11 @@ impl ::std::convert::From for SamplingMessageContent { /// "description": "Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.", /// "type": "object", /// "properties": { +/// "completions": { +/// "description": "Present if the server supports argument autocompletion suggestions.", +/// "type": "object", +/// "additionalProperties": true +/// }, /// "experimental": { /// "description": "Experimental, non-standard capabilities that the server supports.", /// "type": "object", @@ -4774,6 +4912,9 @@ impl ::std::convert::From for SamplingMessageContent { ///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, Default)] pub struct ServerCapabilities { + ///Present if the server supports argument autocompletion suggestions. + #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] + pub completions: ::std::option::Option<::serde_json::Map<::std::string::String, ::serde_json::Value>>, ///Experimental, non-standard capabilities that the server supports. #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] pub experimental: ::std::option::Option< @@ -5341,8 +5482,12 @@ pub struct TextResourceContents { /// "name" /// ], /// "properties": { +/// "annotations": { +/// "description": "Optional additional tool information.", +/// "$ref": "#/definitions/ToolAnnotations" +/// }, /// "description": { -/// "description": "A human-readable description of the tool.", +/// "description": "A human-readable description of the tool.\n\nThis can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a \"hint\" to the model.", /// "type": "string" /// }, /// "inputSchema": { @@ -5381,7 +5526,11 @@ pub struct TextResourceContents { ///
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct Tool { - ///A human-readable description of the tool. + ///Optional additional tool information. + #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] + pub annotations: ::std::option::Option, + /**A human-readable description of the tool. + This can be used by clients to improve the LLM's understanding of available tools. 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>, #[serde(rename = "inputSchema")] @@ -5389,6 +5538,73 @@ pub struct Tool { ///The name of the tool. pub name: ::std::string::String, } +/**Additional properties describing a Tool to clients. +NOTE: all properties in ToolAnnotations are **hints**. +They are not guaranteed to provide a faithful description of +tool behavior (including descriptive properties like title). +Clients should never make tool use decisions based on ToolAnnotations +received from untrusted servers.*/ +/// +///
JSON schema +/// +/// ```json +///{ +/// "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**. \nThey are not guaranteed to provide a faithful description of \ntool behavior (including descriptive properties like title).\n\nClients should never make tool use decisions based on ToolAnnotations\nreceived from untrusted servers.", +/// "type": "object", +/// "properties": { +/// "destructiveHint": { +/// "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when readOnlyHint == false)\n\nDefault: true", +/// "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", +/// "type": "boolean" +/// }, +/// "openWorldHint": { +/// "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true", +/// "type": "boolean" +/// }, +/// "readOnlyHint": { +/// "description": "If true, the tool does not modify its environment.\n\nDefault: false", +/// "type": "boolean" +/// }, +/// "title": { +/// "description": "A human-readable title for the tool.", +/// "type": "string" +/// } +/// } +///} +/// ``` +///
+#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, Default)] +pub struct ToolAnnotations { + /**If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + (This property is meaningful only when readOnlyHint == false) + Default: true*/ + #[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. + (This property is meaningful only when readOnlyHint == false) + Default: false*/ + #[serde(rename = "idempotentHint", default, skip_serializing_if = "::std::option::Option::is_none")] + pub idempotent_hint: ::std::option::Option, + /**If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + Default: true*/ + #[serde(rename = "openWorldHint", default, skip_serializing_if = "::std::option::Option::is_none")] + pub open_world_hint: ::std::option::Option, + /**If true, the tool does not modify its environment. + Default: false*/ + #[serde(rename = "readOnlyHint", default, skip_serializing_if = "::std::option::Option::is_none")] + pub read_only_hint: ::std::option::Option, + ///A human-readable title for the tool. + #[serde(default, skip_serializing_if = "::std::option::Option::is_none")] + pub title: ::std::option::Option<::std::string::String>, +} ///A JSON Schema object defining the expected parameters for the tool. /// ///
JSON schema diff --git a/src/generated_schema/draft/schema_utils.rs b/src/generated_schema/2025_03_26/schema_utils.rs similarity index 100% rename from src/generated_schema/draft/schema_utils.rs rename to src/generated_schema/2025_03_26/schema_utils.rs diff --git a/tarpaulin.toml b/tarpaulin.toml index dd03f74..e4b99dd 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -3,9 +3,9 @@ no-default-features = true features = "2024_11_05 schema_utils" # release = true -[version_draft] +[version_2025_03_26] no-default-features = true -features = "draft schema_utils" +features = "2025_03_26 schema_utils" # release = true [report] diff --git a/tests/version_exclusive.rs b/tests/2024_11_05_exclusive.rs similarity index 100% rename from tests/version_exclusive.rs rename to tests/2024_11_05_exclusive.rs diff --git a/tests/draft_exclusive.rs b/tests/draft_exclusive.rs deleted file mode 100644 index 8740d27..0000000 --- a/tests/draft_exclusive.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Test cases applicable only to the draft version of the schema -#[path = "common/common.rs"] -pub mod common; - -#[cfg(feature = "draft")] -mod test_draft_exclusive { - use super::common::re_serialize; - use rust_mcp_schema::schema_utils::*; - use rust_mcp_schema::*; - - #[test] - fn test_server_list_resources_result() { - let message: ServerMessage = ServerMessage::Response(ServerJsonrpcResponse::new( - RequestId::Integer(15), - ResultFromServer::ServerResult(ServerResult::ListResourcesResult(ListResourcesResult { - meta: None, - next_cursor: None, - resources: vec![Resource { - annotations: None, - description: None, - mime_type: None, - name: "Resource 1".to_string(), - uri: "test://static/resource/1".to_string(), - }], - })), - )); - - let message: ServerMessage = re_serialize(message); - - assert!(matches!(message, ServerMessage::Response(server_message) - if matches!(&server_message.result, ResultFromServer::ServerResult(server_result) - if matches!(server_result, ServerResult::ListResourcesResult(_))) - )); - } -} diff --git a/tests/test_serialize.rs b/tests/test_serialize.rs index 2fc85da..8f0e251 100644 --- a/tests/test_serialize.rs +++ b/tests/test_serialize.rs @@ -264,6 +264,8 @@ mod test_serialize { prompts: None, resources: None, tools: None, + #[cfg(feature = "2025_03_26")] + completions: None, }, instructions: None, meta: None, @@ -823,4 +825,31 @@ mod test_serialize { if server_request["method"] == "notify") ); } + + #[test] + fn test_server_list_resources_result() { + let message: ServerMessage = ServerMessage::Response(ServerJsonrpcResponse::new( + RequestId::Integer(15), + ResultFromServer::ServerResult(ServerResult::ListResourcesResult(ListResourcesResult { + meta: None, + next_cursor: None, + resources: vec![Resource { + annotations: None, + description: None, + mime_type: None, + name: "Resource 1".to_string(), + uri: "test://static/resource/1".to_string(), + #[cfg(feature = "2024_11_05")] + size: None, + }], + })), + )); + + let message: ServerMessage = re_serialize(message); + + assert!(matches!(message, ServerMessage::Response(server_message) + if matches!(&server_message.result, ResultFromServer::ServerResult(server_result) + if matches!(server_result, ServerResult::ListResourcesResult(_))) + )); + } }