diff --git a/crates/rmcp-macros/README.md b/crates/rmcp-macros/README.md index 3aa759aa1..1bd317d16 100644 --- a/crates/rmcp-macros/README.md +++ b/crates/rmcp-macros/README.md @@ -38,12 +38,10 @@ For **getting started** and **full MCP feature documentation**, see the [main RE ## Quick Example ```rust,ignore -use rmcp::{tool, tool_router, tool_handler, ServerHandler, model::*}; +use rmcp::{tool, tool_router, tool_handler, ServerHandler}; #[derive(Clone)] -struct MyServer { - tool_router: rmcp::handler::server::tool::ToolRouter, -} +struct MyServer; #[tool_router] impl MyServer { @@ -54,11 +52,7 @@ impl MyServer { } #[tool_handler] -impl ServerHandler for MyServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::default() - } -} +impl ServerHandler for MyServer {} ``` See the [full documentation](https://docs.rs/rmcp-macros) for detailed usage of each macro. diff --git a/crates/rmcp-macros/src/common.rs b/crates/rmcp-macros/src/common.rs index 877f89955..0ca2fdae2 100644 --- a/crates/rmcp-macros/src/common.rs +++ b/crates/rmcp-macros/src/common.rs @@ -1,7 +1,7 @@ //! Common utilities shared between different macro implementations use quote::quote; -use syn::{Attribute, Expr, FnArg, ImplItemFn, Signature, Type}; +use syn::{Attribute, Expr, FnArg, ImplItem, ImplItemFn, ItemImpl, Signature, Type}; /// Parse a None expression pub fn none_expr() -> syn::Result { @@ -75,3 +75,24 @@ pub fn find_parameters_type_in_sig(sig: &Signature) -> Option> { pub fn find_parameters_type_impl(fn_item: &ImplItemFn) -> Option> { find_parameters_type_in_sig(&fn_item.sig) } + +/// Check whether an `impl` block already contains a method with the given name. +pub fn has_method(name: &str, item_impl: &ItemImpl) -> bool { + item_impl.items.iter().any(|item| match item { + ImplItem::Fn(func) => func.sig.ident == name, + _ => false, + }) +} + +/// Check whether an `impl` block carries a sibling handler attribute (e.g. +/// `#[prompt_handler]` visible from within `#[tool_handler]`). +/// +/// Matches both bare (`prompt_handler`) and path-qualified (`rmcp::prompt_handler`) forms. +pub fn has_sibling_handler(item_impl: &ItemImpl, handler_name: &str) -> bool { + item_impl.attrs.iter().any(|attr| { + attr.path() + .segments + .last() + .is_some_and(|seg| seg.ident == handler_name) + }) +} diff --git a/crates/rmcp-macros/src/lib.rs b/crates/rmcp-macros/src/lib.rs index b04255af5..4258ca1d3 100644 --- a/crates/rmcp-macros/src/lib.rs +++ b/crates/rmcp-macros/src/lib.rs @@ -47,7 +47,9 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream { /// /// It creates a function that returns a `ToolRouter` instance. /// -/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable. +/// The generated function is used by `#[tool_handler]` by default (via `Self::tool_router()`), +/// so in most cases you do not need to store the router in a field. +/// /// ## Usage /// /// | field | type | usage | @@ -62,16 +64,13 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream { /// impl MyToolHandler { /// #[tool] /// pub fn my_tool() { -/// -/// } /// -/// pub fn new() -> Self { -/// Self { -/// // the default name of tool router will be `tool_router` -/// tool_router: Self::tool_router(), -/// } /// } /// } +/// +/// // #[tool_handler] calls Self::tool_router() automatically +/// #[tool_handler] +/// impl ServerHandler for MyToolHandler {} /// ``` /// /// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one: @@ -114,50 +113,62 @@ pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream { /// # tool_handler /// -/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance. +/// This macro generates the `call_tool`, `list_tools`, `get_tool`, and (optionally) +/// `get_info` methods for a `ServerHandler` implementation, using a `ToolRouter`. /// /// ## Usage /// -/// | field | type | usage | -/// | :- | :- | :- | -/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. | -/// ## Example +/// | field | type | usage | +/// | :- | :- | :- | +/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `Self::tool_router()`. | +/// | `meta` | `Expr` | Optional metadata for `ListToolsResult`. | +/// | `name` | `String` | Custom server name. Defaults to `CARGO_CRATE_NAME`. | +/// | `version` | `String` | Custom server version. Defaults to `CARGO_PKG_VERSION`. | +/// | `instructions` | `String` | Optional human-readable instructions about using this server. | +/// +/// ## Minimal example (no boilerplate) +/// +/// The macro automatically generates `get_info()` with tools capability enabled +/// and reads the server name/version from `Cargo.toml`: +/// /// ```rust,ignore -/// #[tool_handler] -/// impl ServerHandler for MyToolHandler { -/// // ...implement other handler +/// struct TimeServer; +/// +/// #[tool_router] +/// impl TimeServer { +/// #[tool(description = "Get current time")] +/// async fn get_time(&self) -> String { "12:00".into() } /// } +/// +/// #[tool_handler] +/// impl ServerHandler for TimeServer {} /// ``` /// -/// or using a custom router expression: +/// ## Custom server info +/// +/// ```rust,ignore +/// #[tool_handler(name = "my-server", version = "1.0.0", instructions = "A helpful server")] +/// impl ServerHandler for MyToolHandler {} +/// ``` +/// +/// ## Custom router expression +/// /// ```rust,ignore -/// #[tool_handler(router = self.get_router().await)] +/// #[tool_handler(router = self.tool_router)] /// impl ServerHandler for MyToolHandler { /// // ...implement other handler /// } /// ``` /// -/// ## Explain +/// ## Manual `get_info()` +/// +/// If you provide your own `get_info()`, the macro will not generate one: /// -/// This macro will be expended to something like this: /// ```rust,ignore +/// #[tool_handler] /// impl ServerHandler for MyToolHandler { -/// async fn call_tool( -/// &self, -/// request: CallToolRequestParams, -/// context: RequestContext, -/// ) -> Result { -/// let tcc = ToolCallContext::new(self, request, context); -/// self.tool_router.call(tcc).await -/// } -/// -/// async fn list_tools( -/// &self, -/// _request: Option, -/// _context: RequestContext, -/// ) -> Result { -/// let items = self.tool_router.list_all(); -/// Ok(ListToolsResult::with_all_items(items)) +/// fn get_info(&self) -> ServerInfo { +/// ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) /// } /// } /// ``` @@ -237,13 +248,16 @@ pub fn prompt_router(attr: TokenStream, input: TokenStream) -> TokenStream { /// # prompt_handler /// -/// This macro generates handler methods for `get_prompt` and `list_prompts` in the implementation block, using an existing `PromptRouter` instance. +/// This macro generates handler methods for `get_prompt` and `list_prompts` in the +/// implementation block, using a `PromptRouter`. It also auto-generates `get_info()` +/// with prompts capability enabled if not already provided. /// /// ## Usage /// /// | field | type | usage | /// | :- | :- | :- | -/// | `router` | `Expr` | The expression to access the `PromptRouter` instance. Defaults to `self.prompt_router`. | +/// | `router` | `Expr` | The expression to access the `PromptRouter` instance. Defaults to `Self::prompt_router()`. | +/// | `meta` | `Expr` | Optional metadata for `ListPromptsResult`. | /// /// ## Example /// ```rust,ignore @@ -255,7 +269,7 @@ pub fn prompt_router(attr: TokenStream, input: TokenStream) -> TokenStream { /// /// or using a custom router expression: /// ```rust,ignore -/// #[prompt_handler(router = self.get_prompt_router())] +/// #[prompt_handler(router = self.prompt_router)] /// impl ServerHandler for MyPromptHandler { /// // ...implement other handler methods /// } diff --git a/crates/rmcp-macros/src/prompt_handler.rs b/crates/rmcp-macros/src/prompt_handler.rs index 7e534f92f..4f2541ac6 100644 --- a/crates/rmcp-macros/src/prompt_handler.rs +++ b/crates/rmcp-macros/src/prompt_handler.rs @@ -3,6 +3,11 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Expr, ImplItem, ItemImpl, parse_quote}; +use crate::{ + common::{has_method, has_sibling_handler}, + tool_handler::{CallerCapability, build_get_info}, +}; + #[derive(FromMeta, Debug, Default)] #[darling(default)] pub struct PromptHandlerAttribute { @@ -22,7 +27,7 @@ pub fn prompt_handler(attr: TokenStream, input: TokenStream) -> syn::Result syn::Result syn::Result { let attr_args = NestedMeta::parse_meta_list(attr)?; let TaskHandlerAttribute { processor } = TaskHandlerAttribute::from_list(&attr_args)?; - let mut item_impl = syn::parse2::(input.clone())?; - - let has_method = |name: &str, item_impl: &ItemImpl| -> bool { - item_impl.items.iter().any(|item| match item { - ImplItem::Fn(func) => func.sig.ident == name, - _ => false, - }) - }; + let mut item_impl = syn::parse2::(input)?; if !has_method("list_tasks", &item_impl) { let list_fn = quote! { @@ -262,5 +257,21 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result(cancel_fn)?); } + // Auto-generate get_info() if not already provided and no sibling tool/prompt handler + // will generate it (they take priority since they run as outer attributes). + if !has_method("get_info", &item_impl) + && !has_sibling_handler(&item_impl, "tool_handler") + && !has_sibling_handler(&item_impl, "prompt_handler") + { + let get_info_fn = crate::tool_handler::build_get_info( + &item_impl, + None, + None, + None, + crate::tool_handler::CallerCapability::Tasks, + )?; + item_impl.items.push(get_info_fn); + } + Ok(item_impl.into_token_stream()) } diff --git a/crates/rmcp-macros/src/tool_handler.rs b/crates/rmcp-macros/src/tool_handler.rs index e28c0ca47..0cb323b5a 100644 --- a/crates/rmcp-macros/src/tool_handler.rs +++ b/crates/rmcp-macros/src/tool_handler.rs @@ -3,39 +3,57 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use syn::{Expr, ImplItem, ItemImpl}; +use crate::common::{has_method, has_sibling_handler}; + #[derive(FromMeta)] #[darling(default)] pub struct ToolHandlerAttribute { pub router: Expr, pub meta: Option, + pub name: Option, + pub version: Option, + pub instructions: Option, } impl Default for ToolHandlerAttribute { fn default() -> Self { Self { router: syn::parse2(quote! { - self.tool_router + Self::tool_router() }) .unwrap(), meta: None, + name: None, + version: None, + instructions: None, } } } pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result { let attr_args = NestedMeta::parse_meta_list(attr)?; - let ToolHandlerAttribute { router, meta } = ToolHandlerAttribute::from_list(&attr_args)?; - let mut item_impl = syn::parse2::(input.clone())?; - let tool_call_fn = quote! { - async fn call_tool( - &self, - request: rmcp::model::CallToolRequestParams, - context: rmcp::service::RequestContext, - ) -> Result { - let tcc = rmcp::handler::server::tool::ToolCallContext::new(self, request, context); - #router.call(tcc).await - } - }; + let ToolHandlerAttribute { + router, + meta, + name, + version, + instructions, + } = ToolHandlerAttribute::from_list(&attr_args)?; + let mut item_impl = syn::parse2::(input)?; + + if !has_method("call_tool", &item_impl) { + let tool_call_fn = syn::parse2::(quote! { + async fn call_tool( + &self, + request: rmcp::model::CallToolRequestParams, + context: rmcp::service::RequestContext, + ) -> Result { + let tcc = rmcp::handler::server::tool::ToolCallContext::new(self, request, context); + #router.call(tcc).await + } + })?; + item_impl.items.push(tool_call_fn); + } let result_meta = if let Some(meta) = meta { quote! { Some(#meta) } @@ -43,31 +61,109 @@ pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result, - _context: rmcp::service::RequestContext, - ) -> Result { - Ok(rmcp::model::ListToolsResult{ - tools: #router.list_all(), - meta: #result_meta, - next_cursor: None, - }) - } - }; + if !has_method("list_tools", &item_impl) { + let tool_list_fn = syn::parse2::(quote! { + async fn list_tools( + &self, + _request: Option, + _context: rmcp::service::RequestContext, + ) -> Result { + Ok(rmcp::model::ListToolsResult{ + tools: #router.list_all(), + meta: #result_meta, + next_cursor: None, + }) + } + })?; + item_impl.items.push(tool_list_fn); + } + + if !has_method("get_tool", &item_impl) { + let get_tool_fn = syn::parse2::(quote! { + fn get_tool(&self, name: &str) -> Option { + #router.get(name).cloned() + } + })?; + item_impl.items.push(get_tool_fn); + } + + // Auto-generate get_info() if not already provided + if !has_method("get_info", &item_impl) { + let get_info_fn = build_get_info( + &item_impl, + name, + version, + instructions, + CallerCapability::Tools, + )?; + item_impl.items.push(get_info_fn); + } + + Ok(item_impl.into_token_stream()) +} + +/// Which handler macro is generating `get_info()`. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum CallerCapability { + Tools, + Prompts, + Tasks, +} + +/// Build a `get_info()` method that returns `ServerInfo` with the appropriate capabilities. +/// +/// The caller declares its own capability via `caller`. Sibling handler attributes +/// (`prompt_handler`, `task_handler`, `tool_handler`) are detected automatically +/// and their capabilities are included. +pub(crate) fn build_get_info( + item_impl: &ItemImpl, + name: Option, + version: Option, + instructions: Option, + caller: CallerCapability, +) -> syn::Result { + let has_tools = + caller == CallerCapability::Tools || has_sibling_handler(item_impl, "tool_handler"); + let has_prompts = + caller == CallerCapability::Prompts || has_sibling_handler(item_impl, "prompt_handler"); + let has_tasks = + caller == CallerCapability::Tasks || has_sibling_handler(item_impl, "task_handler"); + + let mut capability_calls = Vec::new(); + if has_tools { + capability_calls.push(quote! { .enable_tools() }); + } + if has_prompts { + capability_calls.push(quote! { .enable_prompts() }); + } + if has_tasks { + capability_calls.push(quote! { .enable_tasks() }); + } - let get_tool_fn = quote! { - fn get_tool(&self, name: &str) -> Option { - #router.get(name).cloned() + let server_info_expr = match (name, version) { + (Some(n), Some(v)) => quote! { rmcp::model::Implementation::new(#n, #v) }, + (Some(n), None) => { + quote! { rmcp::model::Implementation::new(#n, env!("CARGO_PKG_VERSION")) } } + (None, Some(v)) => { + quote! { rmcp::model::Implementation::new(env!("CARGO_CRATE_NAME"), #v) } + } + (None, None) => quote! { rmcp::model::Implementation::from_build_env() }, }; - let tool_call_fn = syn::parse2::(tool_call_fn)?; - let tool_list_fn = syn::parse2::(tool_list_fn)?; - let get_tool_fn = syn::parse2::(get_tool_fn)?; - item_impl.items.push(tool_call_fn); - item_impl.items.push(tool_list_fn); - item_impl.items.push(get_tool_fn); - Ok(item_impl.into_token_stream()) + let mut builder_calls = vec![quote! { .with_server_info(#server_info_expr) }]; + if let Some(i) = instructions { + builder_calls.push(quote! { .with_instructions(#i.to_string()) }); + } + + syn::parse2::(quote! { + fn get_info(&self) -> rmcp::model::ServerInfo { + rmcp::model::ServerInfo::new( + rmcp::model::ServerCapabilities::builder() + #(#capability_calls)* + .build() + ) + #(#builder_calls)* + } + }) } diff --git a/crates/rmcp/src/handler/server/router/tool.rs b/crates/rmcp/src/handler/server/router/tool.rs index 87f0db6c3..b55934502 100644 --- a/crates/rmcp/src/handler/server/router/tool.rs +++ b/crates/rmcp/src/handler/server/router/tool.rs @@ -5,14 +5,13 @@ //! //! ```rust //! # use rmcp::{ -//! # tool_router, tool, +//! # tool_router, tool, tool_handler, ServerHandler, //! # handler::server::{wrapper::{Parameters, Json}, tool::ToolRouter}, //! # schemars //! # }; //! # use serde::{Serialize, Deserialize}; -//! struct Server { -//! tool_router: ToolRouter, -//! } +//! struct Server; +//! //! #[derive(Deserialize, schemars::JsonSchema, Default)] //! struct AddParameter { //! left: usize, @@ -32,6 +31,9 @@ //! Json(AddOutput { sum: left.wrapping_add(right) }) //! } //! } +//! +//! #[tool_handler] +//! impl ServerHandler for Server {} //! ``` //! //! Using the macro-based code pattern above is suitable for small MCP servers with simple interfaces. diff --git a/crates/rmcp/tests/test_prompt_handler.rs b/crates/rmcp/tests/test_prompt_handler.rs index ca4418bcc..6288cddc0 100644 --- a/crates/rmcp/tests/test_prompt_handler.rs +++ b/crates/rmcp/tests/test_prompt_handler.rs @@ -30,7 +30,7 @@ impl TestPromptServer { } } -#[prompt_handler] +#[prompt_handler(router = self.prompt_router)] impl ServerHandler for TestPromptServer {} #[derive(Debug, Clone)] @@ -80,7 +80,7 @@ impl GenericPromptServer { } } -#[prompt_handler] +#[prompt_handler(router = self.prompt_router)] impl ServerHandler for GenericPromptServer {} #[test] @@ -148,7 +148,7 @@ mod nested { } } - #[prompt_handler] + #[prompt_handler(router = self.prompt_router)] impl ServerHandler for NestedServer {} #[test] diff --git a/crates/rmcp/tests/test_tool_macro_annotations.rs b/crates/rmcp/tests/test_tool_macro_annotations.rs index e945a10fe..34b17625f 100644 --- a/crates/rmcp/tests/test_tool_macro_annotations.rs +++ b/crates/rmcp/tests/test_tool_macro_annotations.rs @@ -19,7 +19,7 @@ mod tests { format!("Direct: {}", input) } } - #[tool_handler] + #[tool_handler(router = self.tool_router)] impl ServerHandler for AnnotatedServer {} #[test] diff --git a/crates/rmcp/tests/test_tool_macros.rs b/crates/rmcp/tests/test_tool_macros.rs index 450a00033..6d3490dfe 100644 --- a/crates/rmcp/tests/test_tool_macros.rs +++ b/crates/rmcp/tests/test_tool_macros.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use rmcp::{ ClientHandler, ServerHandler, ServiceExt, handler::server::{router::tool::ToolRouter, wrapper::Parameters}, - model::{CallToolRequestParams, ClientInfo}, + model::{CallToolRequestParams, ClientInfo, ServerCapabilities, ServerInfo}, tool, tool_handler, tool_router, }; use schemars::JsonSchema; @@ -365,3 +365,153 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> { server_handle.await??; Ok(()) } + +// --- Tests for field-free minimal server pattern (issue #711) --- + +/// Minimal server: no tool_router field, no new(), no get_info(). +#[derive(Debug, Clone)] +pub struct MinimalServer; + +#[tool_router] +impl MinimalServer { + #[tool(description = "Say hello")] + fn hello(&self) -> String { + "hello".to_string() + } +} + +#[tool_handler] +impl ServerHandler for MinimalServer {} + +#[test] +fn test_minimal_server_get_info_auto_generated() { + let server = MinimalServer; + let info = server.get_info(); + + assert!( + info.capabilities.tools.is_some(), + "tools capability should be enabled" + ); + assert!( + info.capabilities.prompts.is_none(), + "prompts should not be auto-enabled" + ); + assert!( + info.capabilities.tasks.is_none(), + "tasks should not be auto-enabled" + ); + assert!( + !info.server_info.name.is_empty(), + "server name should not be empty" + ); + assert!( + !info.server_info.version.is_empty(), + "server version should not be empty" + ); + assert!( + info.instructions.is_none(), + "instructions should be None by default" + ); +} + +#[tokio::test] +async fn test_minimal_server_tool_call() -> anyhow::Result<()> { + let (server_transport, client_transport) = tokio::io::duplex(4096); + + let server_handle = tokio::spawn(async move { + MinimalServer + .serve(server_transport) + .await? + .waiting() + .await?; + anyhow::Ok(()) + }); + + let client = DummyClientHandler::default() + .serve(client_transport) + .await?; + + let result = client + .call_tool(CallToolRequestParams::new("hello")) + .await?; + + let text = result + .content + .first() + .and_then(|c| c.raw.as_text()) + .map(|t| t.text.as_str()) + .expect("Expected text content"); + + assert_eq!(text, "hello"); + + client.cancel().await?; + server_handle.await??; + Ok(()) +} + +/// Server with custom name/version/instructions via tool_handler attributes. +#[derive(Debug, Clone)] +pub struct CustomInfoServer; + +#[tool_router] +impl CustomInfoServer { + #[tool(description = "Ping")] + fn ping(&self) -> String { + "pong".to_string() + } +} + +#[tool_handler( + name = "my-custom-server", + version = "2.0.0", + instructions = "A custom server" +)] +impl ServerHandler for CustomInfoServer {} + +#[test] +fn test_custom_info_server() { + let server = CustomInfoServer; + let info = server.get_info(); + + assert_eq!(info.server_info.name, "my-custom-server"); + assert_eq!(info.server_info.version, "2.0.0"); + assert_eq!(info.instructions.as_deref(), Some("A custom server")); + assert!(info.capabilities.tools.is_some()); +} + +/// Server that provides its own get_info() — macro should not override it. +#[derive(Debug, Clone)] +pub struct ManualInfoServer; + +#[tool_router] +impl ManualInfoServer { + #[tool(description = "Noop")] + fn noop(&self) {} +} + +#[tool_handler] +impl ServerHandler for ManualInfoServer { + fn get_info(&self) -> ServerInfo { + ServerInfo::new( + ServerCapabilities::builder() + .enable_tools() + .enable_resources() + .build(), + ) + .with_server_info(rmcp::model::Implementation::new("manual", "9.9.9")) + } +} + +#[test] +fn test_manual_get_info_not_overridden() { + let server = ManualInfoServer; + let info = server.get_info(); + + assert_eq!(info.server_info.name, "manual"); + assert_eq!(info.server_info.version, "9.9.9"); + assert!(info.capabilities.tools.is_some()); + assert!( + info.capabilities.resources.is_some(), + "manual resources should be preserved" + ); +} diff --git a/examples/servers/src/common/calculator.rs b/examples/servers/src/common/calculator.rs index 2b0ab8e33..c92474bb4 100644 --- a/examples/servers/src/common/calculator.rs +++ b/examples/servers/src/common/calculator.rs @@ -1,10 +1,7 @@ #![allow(dead_code)] use rmcp::{ - ServerHandler, - handler::server::{router::tool::ToolRouter, wrapper::Parameters}, - model::{ServerCapabilities, ServerInfo}, - schemars, tool, tool_handler, tool_router, + ServerHandler, handler::server::wrapper::Parameters, schemars, tool, tool_handler, tool_router, }; #[derive(Debug, serde::Deserialize, schemars::JsonSchema)] @@ -23,16 +20,12 @@ pub struct SubRequest { } #[derive(Debug, Clone)] -pub struct Calculator { - tool_router: ToolRouter, -} +pub struct Calculator; #[tool_router] impl Calculator { pub fn new() -> Self { - Self { - tool_router: Self::tool_router(), - } + Self } #[tool(description = "Calculate the sum of two numbers")] @@ -46,10 +39,5 @@ impl Calculator { } } -#[tool_handler] -impl ServerHandler for Calculator { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) - .with_instructions("A simple calculator".to_string()) - } -} +#[tool_handler(instructions = "A simple calculator")] +impl ServerHandler for Calculator {} diff --git a/examples/servers/src/common/generic_service.rs b/examples/servers/src/common/generic_service.rs index 8034d5214..324f08f0c 100644 --- a/examples/servers/src/common/generic_service.rs +++ b/examples/servers/src/common/generic_service.rs @@ -1,10 +1,7 @@ use std::sync::Arc; use rmcp::{ - ServerHandler, - handler::server::{router::tool::ToolRouter, wrapper::Parameters}, - model::{ServerCapabilities, ServerInfo}, - schemars, tool, tool_handler, tool_router, + ServerHandler, handler::server::wrapper::Parameters, schemars, tool, tool_handler, tool_router, }; #[allow(dead_code)] @@ -41,7 +38,6 @@ impl DataService for MemoryDataService { pub struct GenericService { #[allow(dead_code)] data_service: Arc, - tool_router: ToolRouter, } #[derive(Debug, schemars::JsonSchema, serde::Deserialize, serde::Serialize)] @@ -55,7 +51,6 @@ impl GenericService { pub fn new(data_service: DS) -> Self { Self { data_service: Arc::new(data_service), - tool_router: Self::tool_router(), } } @@ -74,10 +69,5 @@ impl GenericService { } } -#[tool_handler] -impl ServerHandler for GenericService { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) - .with_instructions("generic data service".to_string()) - } -} +#[tool_handler(instructions = "generic data service")] +impl ServerHandler for GenericService {} diff --git a/examples/servers/src/common/progress_demo.rs b/examples/servers/src/common/progress_demo.rs index 1a613e0c7..341b3e70a 100644 --- a/examples/servers/src/common/progress_demo.rs +++ b/examples/servers/src/common/progress_demo.rs @@ -6,8 +6,8 @@ use std::{ use futures::Stream; use rmcp::{ - ErrorData as McpError, RoleServer, ServerHandler, handler::server::tool::ToolRouter, model::*, - service::RequestContext, tool, tool_handler, tool_router, + ErrorData as McpError, RoleServer, ServerHandler, model::*, service::RequestContext, tool, + tool_handler, tool_router, }; use serde_json::json; use tokio_stream::StreamExt; @@ -54,7 +54,6 @@ impl Stream for StreamDataSource { #[derive(Clone)] pub struct ProgressDemo { data_source: StreamDataSource, - tool_router: ToolRouter, } #[tool_router] @@ -62,7 +61,6 @@ impl ProgressDemo { #[allow(dead_code)] pub fn new() -> Self { Self { - tool_router: Self::tool_router(), data_source: StreamDataSource::from_text("Hello, world!"), } } diff --git a/examples/transport/src/common/calculator.rs b/examples/transport/src/common/calculator.rs index f6d4c2a74..ee8ec04f6 100644 --- a/examples/transport/src/common/calculator.rs +++ b/examples/transport/src/common/calculator.rs @@ -2,11 +2,7 @@ use rmcp::{ ServerHandler, - handler::server::{ - router::tool::ToolRouter, - wrapper::{Json, Parameters}, - }, - model::{ServerCapabilities, ServerInfo}, + handler::server::wrapper::{Json, Parameters}, schemars, tool, tool_handler, tool_router, }; @@ -26,15 +22,11 @@ pub struct SubRequest { } #[derive(Debug, Clone)] -pub struct Calculator { - tool_router: ToolRouter, -} +pub struct Calculator; impl Calculator { pub fn new() -> Self { - Self { - tool_router: Self::tool_router(), - } + Self } } @@ -50,10 +42,5 @@ impl Calculator { Json(a - b) } } -#[tool_handler] -impl ServerHandler for Calculator { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) - .with_instructions("A simple calculator") - } -} +#[tool_handler(instructions = "A simple calculator")] +impl ServerHandler for Calculator {} diff --git a/examples/wasi/src/calculator.rs b/examples/wasi/src/calculator.rs index a6f63fbe5..f11792763 100644 --- a/examples/wasi/src/calculator.rs +++ b/examples/wasi/src/calculator.rs @@ -2,11 +2,7 @@ use rmcp::{ ServerHandler, - handler::server::{ - router::tool::ToolRouter, - wrapper::{Json, Parameters}, - }, - model::{ServerCapabilities, ServerInfo}, + handler::server::wrapper::{Json, Parameters}, schemars, tool, tool_handler, tool_router, }; @@ -25,22 +21,12 @@ pub struct SubRequest { pub b: i32, } -#[derive(Debug, Clone)] -pub struct Calculator { - tool_router: ToolRouter, -} +#[derive(Debug, Clone, Default)] +pub struct Calculator; impl Calculator { pub fn new() -> Self { - Self { - tool_router: Self::tool_router(), - } - } -} - -impl Default for Calculator { - fn default() -> Self { - Self::new() + Self } } @@ -57,10 +43,5 @@ impl Calculator { } } -#[tool_handler] -impl ServerHandler for Calculator { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) - .with_instructions("A simple calculator") - } -} +#[tool_handler(instructions = "A simple calculator")] +impl ServerHandler for Calculator {}