Skip to content

feat: improve schema version configuration using Cargo features #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ rust-mcp-sdk = { path = "crates/rust-mcp-sdk", default-features = false }
rust-mcp-macros = { version = "0.4.1", path = "crates/rust-mcp-macros", default-features = false }

# External crates
rust-mcp-schema = { version = "0.5", default-features = false }
rust-mcp-schema = { version = "0.6", default-features = false }

futures = { version = "0.3" }
tokio = { version = "1.4", features = ["full"] }
serde = { version = "1.0", features = ["derive", "serde_derive"] }
Expand Down
9 changes: 7 additions & 2 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ args = ["nextest", "run", "--no-tests=pass"]
[tasks.doc-test]
workspace = false
command = "cargo"
args = ["test", "--doc", "-p", "rust-mcp-sdk", "-p", "rust-mcp-transport"]
dependencies = ["doc-test-macros"]
args = ["test", "--doc", "-p", "rust-mcp-sdk"]
dependencies = ["doc-test-macros", "doc-test-transport"]

[tasks.doc-test-transport]
workspace = false
command = "cargo"
args = ["test", "--doc", "-p", "rust-mcp-sdk"]

[tasks.doc-test-macros]
workspace = false
Expand Down
129 changes: 63 additions & 66 deletions crates/rust-mcp-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,25 @@ use utils::{is_option, renamed_field, type_to_json_schema};
/// * `name` - An optional string representing the tool's name.
/// * `description` - An optional string describing the tool.
///
#[cfg(feature = "2024_11_05")]
struct McpToolMacroAttributes {
name: Option<String>,
description: Option<String>,
}

/// Represents the attributes for the `mcp_tool` procedural macro.
///
/// This struct parses and validates the `name` and `description` attributes provided
/// to the `mcp_tool` macro. Both attributes are required and must not be empty strings.
///
/// # Fields
/// * `name` - An optional string representing the tool's name.
/// * `description` - An optional string describing the tool.
/// The following fields are available only with the `2025_03_26` feature:
/// * `destructive_hint` - Optional boolean for `ToolAnnotations::destructive_hint`.
/// * `idempotent_hint` - Optional boolean for `ToolAnnotations::idempotent_hint`.
/// * `open_world_hint` - Optional boolean for `ToolAnnotations::open_world_hint`.
/// * `read_only_hint` - Optional boolean for `ToolAnnotations::read_only_hint`.
/// * `title` - Optional string for `ToolAnnotations::title`.
///
#[cfg(feature = "2025_03_26")]
struct McpToolMacroAttributes {
name: Option<String>,
description: Option<String>,
#[cfg(feature = "2025_03_26")]
destructive_hint: Option<bool>,
#[cfg(feature = "2025_03_26")]
idempotent_hint: Option<bool>,
#[cfg(feature = "2025_03_26")]
open_world_hint: Option<bool>,
#[cfg(feature = "2025_03_26")]
read_only_hint: Option<bool>,
#[cfg(feature = "2025_03_26")]
title: Option<String>,
}

Expand Down Expand Up @@ -75,13 +66,20 @@ impl Parse for McpToolMacroAttributes {
/// - The `name` attribute is missing or empty.
/// - The `description` attribute is missing or empty.
fn parse(attributes: syn::parse::ParseStream) -> syn::Result<Self> {
let mut name = None;
let mut description = None;
let mut destructive_hint = None;
let mut idempotent_hint = None;
let mut open_world_hint = None;
let mut read_only_hint = None;
let mut title = None;
let mut instance = Self {
name: None,
description: None,
#[cfg(feature = "2025_03_26")]
destructive_hint: None,
#[cfg(feature = "2025_03_26")]
idempotent_hint: None,
#[cfg(feature = "2025_03_26")]
open_world_hint: None,
#[cfg(feature = "2025_03_26")]
read_only_hint: None,
#[cfg(feature = "2025_03_26")]
title: None,
};

let meta_list: Punctuated<Meta, Token![,]> = Punctuated::parse_terminated(attributes)?;
for meta in meta_list {
Expand Down Expand Up @@ -131,33 +129,38 @@ impl Parse for McpToolMacroAttributes {
}
};
match ident_str.as_str() {
"name" => name = Some(value),
"description" => description = Some(value),
"name" => instance.name = Some(value),
"description" => instance.description = Some(value),
_ => {}
}
}
"destructive_hint" | "idempotent_hint" | "open_world_hint"
| "read_only_hint" => {
let value = match &meta_name_value.value {
Expr::Lit(ExprLit {
lit: Lit::Bool(lit_bool),
..
}) => lit_bool.value,
_ => {
return Err(Error::new_spanned(
&meta_name_value.value,
"Expected a boolean literal",
));
#[cfg(feature = "2025_03_26")]
{
let value = match &meta_name_value.value {
Expr::Lit(ExprLit {
lit: Lit::Bool(lit_bool),
..
}) => lit_bool.value,
_ => {
return Err(Error::new_spanned(
&meta_name_value.value,
"Expected a boolean literal",
));
}
};

match ident_str.as_str() {
"destructive_hint" => instance.destructive_hint = Some(value),
"idempotent_hint" => instance.idempotent_hint = Some(value),
"open_world_hint" => instance.open_world_hint = Some(value),
"read_only_hint" => instance.read_only_hint = Some(value),
_ => {}
}
};
match ident_str.as_str() {
"destructive_hint" => destructive_hint = Some(value),
"idempotent_hint" => idempotent_hint = Some(value),
"open_world_hint" => open_world_hint = Some(value),
"read_only_hint" => read_only_hint = Some(value),
_ => {}
}
}
#[cfg(feature = "2025_03_26")]
"title" => {
let value = match &meta_name_value.value {
Expr::Lit(ExprLit {
Expand All @@ -171,21 +174,27 @@ impl Parse for McpToolMacroAttributes {
));
}
};
title = Some(value);
instance.title = Some(value);
}
_ => {}
}
}
}

// Validate presence and non-emptiness
if name.as_ref().map(|s| s.trim().is_empty()).unwrap_or(true) {
if instance
.name
.as_ref()
.map(|s| s.trim().is_empty())
.unwrap_or(true)
{
return Err(Error::new(
attributes.span(),
"The 'name' attribute is required and must not be empty.",
));
}
if description
if instance
.description
.as_ref()
.map(|s| s.trim().is_empty())
.unwrap_or(true)
Expand All @@ -196,20 +205,6 @@ impl Parse for McpToolMacroAttributes {
));
}

#[cfg(feature = "2024_11_05")]
let instance = Self { name, description };

#[cfg(feature = "2025_03_26")]
let instance = Self {
name,
description,
destructive_hint,
idempotent_hint,
open_world_hint,
read_only_hint,
title,
};

Ok(instance)
}
}
Expand Down Expand Up @@ -304,21 +299,23 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream {
quote! {None}
};

#[cfg(feature = "2025_03_26")]
let tool_token = quote! {
#base_crate::Tool {
name: #tool_name.to_string(),
description: Some(#tool_description.to_string()),
input_schema: #base_crate::ToolInputSchema::new(required, properties),
annotations: #annotations
let annotations_token = {
#[cfg(feature = "2025_03_26")]
{
quote! { annotations: #annotations }
}
#[cfg(not(feature = "2025_03_26"))]
{
quote! {}
}
};
#[cfg(feature = "2024_11_05")]

let tool_token = quote! {
#base_crate::Tool {
name: #tool_name.to_string(),
description: Some(#tool_description.to_string()),
input_schema: #base_crate::ToolInputSchema::new(required, properties),
#annotations_token
}
};

Expand Down
26 changes: 15 additions & 11 deletions crates/rust-mcp-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ license = "MIT"
edition = "2021"

[dependencies]
rust-mcp-schema = { workspace = true, default-features=false }
rust-mcp-schema = { workspace = true, default-features = false }
rust-mcp-transport = { workspace = true, default-features = false, optional = true }
rust-mcp-macros = { workspace = true, optional = true, default-features=false, feature=["sdk"] }
rust-mcp-macros = { workspace = true, optional = true, default-features = false }

tokio.workspace = true
serde = { workspace = true }
Expand Down Expand Up @@ -46,7 +46,7 @@ default = [
"macros",
"hyper-server",
"ssl",
"2025_03_26"
"2025_03_26",
] # All features enabled by default
server = ["rust-mcp-transport/stdio"] # Server feature
client = ["rust-mcp-transport/stdio", "rust-mcp-transport/sse"] # Client feature
Expand All @@ -61,16 +61,20 @@ ssl = ["axum-server/tls-rustls"]
macros = ["rust-mcp-macros/sdk"]

# enabled mcp protocol version 2025_03_26
2025_03_26 = ["rust-mcp-schema/2025_03_26",
"rust-mcp-macros/2025_03_26",
"rust-mcp-transport/2025_03_26",
"rust-mcp-schema/schema_utils"]
2025_03_26 = [
"rust-mcp-schema/2025_03_26",
"rust-mcp-macros/2025_03_26",
"rust-mcp-transport/2025_03_26",
"rust-mcp-schema/schema_utils",
]

# enabled mcp protocol version 2024_11_05
2024_11_05 = ["rust-mcp-schema/2024_11_05",
"rust-mcp-macros/2024_11_05",
"rust-mcp-transport/2024_11_05",
"rust-mcp-schema/schema_utils"]
2024_11_05 = [
"rust-mcp-schema/2024_11_05",
"rust-mcp-macros/2024_11_05",
"rust-mcp-transport/2024_11_05",
"rust-mcp-schema/schema_utils",
]

[lints]
workspace = true
4 changes: 2 additions & 2 deletions crates/rust-mcp-sdk/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rust_mcp_schema::RpcError;
use crate::schema::RpcError;
use rust_mcp_transport::error::TransportError;
use thiserror::Error;

Expand All @@ -18,7 +18,7 @@ pub enum McpSdkError {
#[error("{0}")]
AnyError(Box<(dyn std::error::Error + Send + Sync)>),
#[error("{0}")]
SdkError(#[from] rust_mcp_schema::schema_utils::SdkError),
SdkError(#[from] crate::schema::schema_utils::SdkError),
#[cfg(feature = "hyper-server")]
#[error("{0}")]
TransportServerError(#[from] TransportServerError),
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-mcp-sdk/src/hyper_servers/app_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{sync::Arc, time::Duration};

use rust_mcp_schema::InitializeResult;
use crate::schema::InitializeResult;
use rust_mcp_transport::TransportOptions;

use crate::mcp_traits::mcp_handler::McpServerHandler;
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-mcp-sdk/src/hyper_servers/hyper_server.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Arc;

use rust_mcp_schema::InitializeResult;
use crate::schema::InitializeResult;

use crate::mcp_server::{server_runtime::ServerRuntimeInternalHandler, ServerHandler};

Expand Down
2 changes: 1 addition & 1 deletion crates/rust-mcp-sdk/src/hyper_servers/hyper_server_core.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{HyperServer, HyperServerOptions};
use crate::mcp_server::{server_runtime_core::RuntimeCoreInternalHandler, ServerHandlerCore};
use rust_mcp_schema::InitializeResult;
use crate::schema::InitializeResult;
use std::sync::Arc;

/// Creates a new HyperServer instance with the provided handler and options
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-mcp-sdk/src/hyper_servers/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use super::{
routes::app_routes,
IdGenerator, InMemorySessionStore, UuidGenerator,
};
use crate::schema::InitializeResult;
use axum::Router;
use rust_mcp_schema::InitializeResult;
use rust_mcp_transport::TransportOptions;

// Default client ping interval (12 seconds)
Expand Down
4 changes: 1 addition & 3 deletions crates/rust-mcp-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,4 @@ pub mod macros {
pub use rust_mcp_macros::*;
}

pub mod schema {
pub use rust_mcp_schema::*;
}
pub mod schema;
4 changes: 2 additions & 2 deletions crates/rust-mcp-sdk/src/mcp_handlers/mcp_client_handler.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use async_trait::async_trait;
use rust_mcp_schema::{
use crate::schema::{
CancelledNotification, CreateMessageRequest, CreateMessageResult, ListRootsRequest,
ListRootsResult, LoggingMessageNotification, PingRequest, ProgressNotification,
PromptListChangedNotification, ResourceListChangedNotification, ResourceUpdatedNotification,
Result, RpcError, ToolListChangedNotification,
};
use async_trait::async_trait;
use serde_json::Value;

use crate::mcp_traits::mcp_client::McpClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::schema::schema_utils::*;
use crate::schema::*;
use async_trait::async_trait;
use rust_mcp_schema::schema_utils::*;
use rust_mcp_schema::*;

use crate::mcp_traits::mcp_client::McpClient;

Expand Down
2 changes: 1 addition & 1 deletion crates/rust-mcp-sdk/src/mcp_handlers/mcp_server_handler.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::schema::{schema_utils::CallToolError, *};
use async_trait::async_trait;
use rust_mcp_schema::{schema_utils::CallToolError, *};
use serde_json::Value;

use crate::{mcp_traits::mcp_server::McpServer, utils::enforce_compatible_protocol_version};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::schema::schema_utils::*;
use crate::schema::*;
use async_trait::async_trait;
use rust_mcp_schema::schema_utils::*;
use rust_mcp_schema::*;

use crate::mcp_traits::mcp_server::McpServer;

Expand Down
Loading
Loading