use darling::{FromMeta, ast::NestedMeta}; 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() }) .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, 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) } } else { quote! { 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 { let supports_cache_hints = context.protocol_version().is_some_and(|version| { version >= rmcp::model::ProtocolVersion::V_2026_07_28 }); Ok(rmcp::model::ListToolsResult{ result_type: Some(rmcp::model::ResultType::COMPLETE), tools: #router.list_all(), meta: #result_meta, next_cursor: None, ttl_ms: supports_cache_hints.then_some(0), cache_scope: supports_cache_hints .then_some(rmcp::model::CacheScope::Public), }) } })?; 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, } /// 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`, `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 mut capability_calls = Vec::new(); if has_tools { capability_calls.push(quote! { .enable_tools() }); } if has_prompts { capability_calls.push(quote! { .enable_prompts() }); } 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 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)* } }) }