//! Tools for MCP servers. //! //! It's straightforward to define tools using [`tool_router`][crate::tool_router] and //! [`tool`][crate::tool] macro. //! //! ```rust //! # use rmcp::{ //! # tool_router, tool, //! # handler::server::{wrapper::{Parameters, Json}, tool::ToolRouter}, //! # schemars //! # }; //! # use serde::{Serialize, Deserialize}; //! struct Server { //! tool_router: ToolRouter, //! } //! #[derive(Deserialize, schemars::JsonSchema, Default)] //! struct AddParameter { //! left: usize, //! right: usize //! } //! #[derive(Serialize, schemars::JsonSchema)] //! struct AddOutput { //! sum: usize //! } //! #[tool_router] //! impl Server { //! #[tool(name = "adder", description = "Modular add two integers")] //! fn add( //! &self, //! Parameters(AddParameter { left, right }): Parameters //! ) -> Json { //! Json(AddOutput { sum: left.wrapping_add(right) }) //! } //! } //! ``` //! //! Using the macro-based code pattern above is suitable for small MCP servers with simple interfaces. //! When the business logic become larger, it is recommended that each tool should reside //! in individual file, combined into MCP server using [`SyncTool`] and [`AsyncTool`] traits. //! //! ```rust //! # use rmcp::{ //! # handler::server::{ //! # tool::ToolRouter, //! # router::tool::{SyncTool, AsyncTool, ToolBase}, //! # }, //! # schemars, ErrorData //! # }; //! # pub struct MyCustomError; //! # impl From for ErrorData { //! # fn from(err: MyCustomError) -> ErrorData { unimplemented!() } //! # } //! # use serde::{Serialize, Deserialize}; //! # use std::borrow::Cow; //! // In tool1.rs //! pub struct ComplexTool1; //! #[derive(Deserialize, schemars::JsonSchema, Default)] //! pub struct ComplexTool1Input { /* ... */ } //! #[derive(Serialize, schemars::JsonSchema)] //! pub struct ComplexTool1Output { /* ... */ } //! //! impl ToolBase for ComplexTool1 { //! type Parameter = ComplexTool1Input; //! type Output = ComplexTool1Output; //! type Error = MyCustomError; //! fn name() -> Cow<'static, str> { //! "complex-tool1".into() //! } //! //! fn description() -> Option> { //! Some("...".into()) //! } //! } //! impl SyncTool for ComplexTool1 { //! fn invoke(service: &MyToolServer, param: Self::Parameter) -> Result { //! // ... //! # unimplemented!() //! } //! } //! // In tool2.rs //! pub struct ComplexTool2; //! #[derive(Deserialize, schemars::JsonSchema, Default)] //! pub struct ComplexTool2Input { /* ... */ } //! #[derive(Serialize, schemars::JsonSchema)] //! pub struct ComplexTool2Output { /* ... */ } //! //! impl ToolBase for ComplexTool2 { //! type Parameter = ComplexTool2Input; //! type Output = ComplexTool2Output; //! type Error = MyCustomError; //! fn name() -> Cow<'static, str> { //! "complex-tool2".into() //! } //! //! fn description() -> Option> { //! Some("...".into()) //! } //! } //! impl AsyncTool for ComplexTool2 { //! async fn invoke(service: &MyToolServer, param: Self::Parameter) -> Result { //! // ... //! # unimplemented!() //! } //! } //! //! // In tool_router.rs //! struct MyToolServer { //! tool_router: ToolRouter, //! } //! impl MyToolServer { //! pub fn tool_router() -> ToolRouter { //! ToolRouter::new() //! .with_sync_tool::() //! .with_async_tool::() //! } //! } //! ``` //! //! It's also possible to use macro-based and trait-based tool definition together: Since //! [`ToolRouter`] implements [`Add`][std::ops::Add], you can add two tool routers into final //! router as showed in [the documentation of `tool_router`][crate::tool_router]. mod tool_traits; use std::{borrow::Cow, sync::Arc}; use schemars::JsonSchema; pub use tool_traits::{AsyncTool, SyncTool, ToolBase}; use crate::{ handler::server::{ tool::{CallToolHandler, DynCallToolHandler, ToolCallContext, schema_for_type}, tool_name_validation::validate_and_warn_tool_name, }, model::{CallToolResult, Tool, ToolAnnotations}, service::{MaybeBoxFuture, MaybeSend}, }; pub struct ToolRoute { #[allow(clippy::type_complexity)] pub call: Arc>, pub attr: crate::model::Tool, } impl std::fmt::Debug for ToolRoute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ToolRoute") .field("name", &self.attr.name) .field("description", &self.attr.description) .field("input_schema", &self.attr.input_schema) .finish() } } impl Clone for ToolRoute { fn clone(&self) -> Self { Self { call: self.call.clone(), attr: self.attr.clone(), } } } impl ToolRoute { pub fn new(attr: impl Into, call: C) -> Self where C: CallToolHandler + MaybeSend + Clone + 'static, { Self { call: Arc::new(move |context: ToolCallContext| { let call = call.clone(); context.invoke(call) }), attr: attr.into(), } } pub fn new_dyn(attr: impl Into, call: C) -> Self where C: for<'a> Fn( ToolCallContext<'a, S>, ) -> MaybeBoxFuture<'a, Result> + MaybeSend + 'static, { Self { call: Arc::new(call), attr: attr.into(), } } pub fn name(&self) -> &str { &self.attr.name } } pub trait IntoToolRoute { fn into_tool_route(self) -> ToolRoute; } impl IntoToolRoute for (T, C) where S: MaybeSend + 'static, C: CallToolHandler + MaybeSend + Clone + 'static, T: Into, { fn into_tool_route(self) -> ToolRoute { ToolRoute::new(self.0.into(), self.1) } } impl IntoToolRoute for ToolRoute where S: MaybeSend + 'static, { fn into_tool_route(self) -> ToolRoute { self } } pub struct ToolAttrGenerateFunctionAdapter; impl IntoToolRoute for F where S: MaybeSend + 'static, F: Fn() -> ToolRoute, { fn into_tool_route(self) -> ToolRoute { (self)() } } pub trait CallToolHandlerExt: Sized where Self: CallToolHandler + MaybeSend + Clone + 'static, { fn name(self, name: impl Into>) -> WithToolAttr; } impl CallToolHandlerExt for C where C: CallToolHandler + MaybeSend + Clone + 'static, { fn name(self, name: impl Into>) -> WithToolAttr { WithToolAttr { attr: Tool::new( name.into(), "", schema_for_type::(), ), call: self, _marker: std::marker::PhantomData, } } } pub struct WithToolAttr where C: CallToolHandler + MaybeSend + Clone + 'static, { pub attr: crate::model::Tool, pub call: C, pub _marker: std::marker::PhantomData, } impl IntoToolRoute for WithToolAttr where C: CallToolHandler + MaybeSend + Clone + 'static, S: MaybeSend + 'static, { fn into_tool_route(self) -> ToolRoute { ToolRoute::new(self.attr, self.call) } } impl WithToolAttr where C: CallToolHandler + MaybeSend + Clone + 'static, { pub fn description(mut self, description: impl Into>) -> Self { self.attr.description = Some(description.into()); self } pub fn parameters(mut self) -> Self { self.attr.input_schema = schema_for_type::(); self } pub fn parameters_value(mut self, schema: serde_json::Value) -> Self { self.attr.input_schema = crate::model::object(schema).into(); self } pub fn annotation(mut self, annotation: impl Into) -> Self { self.attr.annotations = Some(annotation.into()); self } } #[derive(Debug)] pub struct ToolRouter { #[allow(clippy::type_complexity)] pub map: std::collections::HashMap, ToolRoute>, pub transparent_when_not_found: bool, } impl Default for ToolRouter { fn default() -> Self { Self { map: std::collections::HashMap::new(), transparent_when_not_found: false, } } } impl Clone for ToolRouter { fn clone(&self) -> Self { Self { map: self.map.clone(), transparent_when_not_found: self.transparent_when_not_found, } } } impl IntoIterator for ToolRouter { type Item = ToolRoute; type IntoIter = std::collections::hash_map::IntoValues, ToolRoute>; fn into_iter(self) -> Self::IntoIter { self.map.into_values() } } impl ToolRouter where S: MaybeSend + 'static, { pub fn new() -> Self { Self { map: std::collections::HashMap::new(), transparent_when_not_found: false, } } pub fn with_route(mut self, route: R) -> Self where R: IntoToolRoute, { self.add_route(route.into_tool_route()); self } /// Add a tool that implements [`SyncTool`] pub fn with_sync_tool(self) -> Self where T: SyncTool + 'static, { if T::input_schema().is_some() { self.with_route(( tool_traits::tool_attribute::(), tool_traits::sync_tool_wrapper::, )) } else { self.with_route(( tool_traits::tool_attribute::(), tool_traits::sync_tool_wrapper_with_empty_params::, )) } } /// Add a tool that implements [`AsyncTool`] pub fn with_async_tool(self) -> Self where T: AsyncTool + 'static, { if T::input_schema().is_some() { self.with_route(( tool_traits::tool_attribute::(), tool_traits::async_tool_wrapper::, )) } else { self.with_route(( tool_traits::tool_attribute::(), tool_traits::async_tool_wrapper_with_empty_params::, )) } } pub fn add_route(&mut self, item: ToolRoute) { let new_name = &item.attr.name; validate_and_warn_tool_name(new_name); self.map.insert(new_name.clone(), item); } pub fn merge(&mut self, other: ToolRouter) { for item in other.map.into_values() { self.add_route(item); } } pub fn remove_route(&mut self, name: &str) { self.map.remove(name); } pub fn has_route(&self, name: &str) -> bool { self.map.contains_key(name) } pub async fn call( &self, context: ToolCallContext<'_, S>, ) -> Result { let item = self .map .get(context.name()) .ok_or_else(|| crate::ErrorData::invalid_params("tool not found", None))?; let result = (item.call)(context).await?; Ok(result) } pub fn list_all(&self) -> Vec { let mut tools: Vec<_> = self.map.values().map(|item| item.attr.clone()).collect(); tools.sort_by(|a, b| a.name.cmp(&b.name)); tools } /// Get a tool definition by name. /// /// Returns the tool if found, or `None` if no tool with the given name exists. pub fn get(&self, name: &str) -> Option<&crate::model::Tool> { self.map.get(name).map(|r| &r.attr) } } impl std::ops::Add> for ToolRouter where S: MaybeSend + 'static, { type Output = Self; fn add(mut self, other: ToolRouter) -> Self::Output { self.merge(other); self } } impl std::ops::AddAssign> for ToolRouter where S: MaybeSend + 'static, { fn add_assign(&mut self, other: ToolRouter) { self.merge(other); } }