rust-sdk/crates/rmcp-macros/src/tool_router.rs
4t145 1f7f4d3055
refactor: refactor tool macros and router implementation (#261)
* refactor: refactor tool macros and router implementation

- Updated the `#[tool(tool_box)]` macro to `#[tool_router]` across various modules for consistency.
- Enhanced the `Calculator`, `Counter`, and `GenericService` structs to utilize `ToolRouter` for handling tool calls.
- Introduced `Parameters` struct for better parameter handling in tool functions.
- Added new methods for listing tools and calling tools in server handlers.
- Improved test cases to reflect changes in tool routing and parameter handling.
- Updated documentation and examples to align with the new router structure.

* fix: fix fmt and build error

* fix: fix test failure

* docs: documents for macros, fix ci

* fix: fix ci

* fix: fix wrongly replaced documents

* fix: remove useless file

* fix: change the parameter format for tool_router

* fix: update extract_doc_line to handle existing documentation and clean up unused code in server handler

* doc: update document for macro and examples

* doc: update readme and add contribute guide

* fix: fix type
2025-06-24 04:01:22 +08:00

90 lines
2.6 KiB
Rust

//! ```ignore
//! #[rmcp::tool_router(router)]
//! impl Handler {
//!
//! }
//! ```
//!
use darling::{FromMeta, ast::NestedMeta};
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{Ident, ImplItem, ItemImpl, Visibility};
#[derive(FromMeta)]
#[darling(default)]
pub struct ToolRouterAttribute {
pub router: Ident,
pub vis: Option<Visibility>,
}
impl Default for ToolRouterAttribute {
fn default() -> Self {
Self {
router: format_ident!("tool_router"),
vis: None,
}
}
}
pub fn tool_router(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
let attr_args = NestedMeta::parse_meta_list(attr)?;
let ToolRouterAttribute { router, vis } = ToolRouterAttribute::from_list(&attr_args)?;
let mut item_impl = syn::parse2::<ItemImpl>(input.clone())?;
// find all function marked with `#[rmcp::tool]`
let tool_attr_fns: Vec<_> = item_impl
.items
.iter()
.filter_map(|item| {
if let syn::ImplItem::Fn(fn_item) = item {
fn_item
.attrs
.iter()
.any(|attr| {
attr.path()
.segments
.last()
.is_some_and(|seg| seg.ident == "tool")
})
.then_some(&fn_item.sig.ident)
} else {
None
}
})
.collect();
let mut routers = vec![];
for handler in tool_attr_fns {
let tool_attr_fn_ident = format_ident!("{handler}_tool_attr");
routers.push(quote! {
.with_route((Self::#tool_attr_fn_ident(), Self::#handler))
})
}
let router_fn = syn::parse2::<ImplItem>(quote! {
#vis fn #router() -> rmcp::handler::server::router::tool::ToolRouter<Self> {
rmcp::handler::server::router::tool::ToolRouter::<Self>::new()
#(#routers)*
}
})?;
item_impl.items.push(router_fn);
Ok(item_impl.into_token_stream())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_router_attr() -> Result<(), Box<dyn std::error::Error>> {
let attr = quote! {
router = test_router,
};
let attr_args = NestedMeta::parse_meta_list(attr)?;
let ToolRouterAttribute { router, vis } = ToolRouterAttribute::from_list(&attr_args)?;
println!("router: {}", router);
if let Some(vis) = vis {
println!("visibility: {}", vis.to_token_stream());
} else {
println!("visibility: None");
}
Ok(())
}
}