rmcp-macros 1.3.0

Rust SDK for Model Context Protocol macros library
Documentation
use darling::{FromMeta, ast::NestedMeta};
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{Expr, ImplItem, ItemImpl};

#[derive(FromMeta)]
#[darling(default)]
pub struct ToolHandlerAttribute {
    pub router: Expr,
    pub meta: Option<Expr>,
}

impl Default for ToolHandlerAttribute {
    fn default() -> Self {
        Self {
            router: syn::parse2(quote! {
                self.tool_router
            })
            .unwrap(),
            meta: None,
        }
    }
}

pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
    let attr_args = NestedMeta::parse_meta_list(attr)?;
    let ToolHandlerAttribute { router, meta } = ToolHandlerAttribute::from_list(&attr_args)?;
    let mut item_impl = syn::parse2::<ItemImpl>(input.clone())?;
    let tool_call_fn = quote! {
        async fn call_tool(
            &self,
            request: rmcp::model::CallToolRequestParams,
            context: rmcp::service::RequestContext<rmcp::RoleServer>,
        ) -> Result<rmcp::model::CallToolResult, rmcp::ErrorData> {
            let tcc = rmcp::handler::server::tool::ToolCallContext::new(self, request, context);
            #router.call(tcc).await
        }
    };

    let result_meta = if let Some(meta) = meta {
        quote! { Some(#meta) }
    } else {
        quote! { None }
    };

    let tool_list_fn = quote! {
        async fn list_tools(
            &self,
            _request: Option<rmcp::model::PaginatedRequestParams>,
            _context: rmcp::service::RequestContext<rmcp::RoleServer>,
        ) -> Result<rmcp::model::ListToolsResult, rmcp::ErrorData> {
            Ok(rmcp::model::ListToolsResult{
                tools: #router.list_all(),
                meta: #result_meta,
                next_cursor: None,
            })
        }
    };

    let get_tool_fn = quote! {
        fn get_tool(&self, name: &str) -> Option<rmcp::model::Tool> {
            #router.get(name).cloned()
        }
    };

    let tool_call_fn = syn::parse2::<ImplItem>(tool_call_fn)?;
    let tool_list_fn = syn::parse2::<ImplItem>(tool_list_fn)?;
    let get_tool_fn = syn::parse2::<ImplItem>(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())
}