Skip to main content

tool_router

Attribute Macro tool_router 

Source
#[tool_router]
Available on crate features macros and server only.
Expand description

§tool_router

This macro is used to generate a tool router based on functions marked with #[rmcp::tool] in an implementation block.

It creates a function that returns a ToolRouter instance.

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

fieldtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.
server_handlerflagWhen set, also emits #[::rmcp::tool_handler] on impl ServerHandler for Self so you can omit a separate #[tool_handler] block.
allow_emptyflagWhen set, accepts an impl block with no #[tool] fn. Without it, an empty router is a compile error.

§Example

#[tool_router]
impl MyToolHandler {
    #[tool]
    pub fn my_tool() {

    }
}

// #[tool_handler] calls Self::tool_router() automatically
#[tool_handler]
impl ServerHandler for MyToolHandler {}

§Eliding #[tool_handler]

For a tools-only server, pass server_handler so the impl ServerHandler block is not written by hand:

#[tool_router(server_handler)]
impl MyToolHandler {
    #[tool]
    fn my_tool() {}
}

This expands in two steps: first #[tool_router] emits the inherent impl plus #[::rmcp::tool_handler] impl ServerHandler for MyToolHandler {}, then #[tool_handler] fills in call_tool, list_tools, get_info, and related methods. If you combine tools with prompts or tasks on the same impl ServerHandler block (stacked #[tool_handler] / #[prompt_handler] attributes), keep using an explicit #[tool_handler] impl instead of server_handler.

Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:

mod a {
    #[tool_router(router = tool_router_a, vis = "pub")]
    impl MyToolHandler {
        #[tool]
        fn my_tool_a() {

        }
    }
}

mod b {
    #[tool_router(router = tool_router_b, vis = "pub")]
    impl MyToolHandler {
        #[tool]
        fn my_tool_b() {

        }
    }
}

impl MyToolHandler {
    fn new() -> Self {
        Self {
            tool_router: self::tool_router_a() + self::tool_router_b(),
        }
    }
}

§Empty routers

Collecting tools is this attribute’s whole purpose, so an impl block with no #[tool] fn is a compile error rather than a router that silently serves nothing. Pass allow_empty when that is what you want:

#[tool_router(allow_empty)]
impl MyToolHandler {}

The usual way to hit this by accident is a macro_rules! helper inside the impl block. An attribute macro receives the unexpanded item, so #[tool] fns produced by such a helper are invisible to #[tool_router]. Let the macro_rules! emit the whole annotated impl instead:

macro_rules! define_tools {
    ($($name:ident => $description:literal),* $(,)?) => {
        #[tool_router]
        impl MyToolHandler {
            $(
                #[tool(description = $description)]
                async fn $name(&self) -> String { stringify!($name).to_owned() }
            )*
        }
    };
}

define_tools!(my_tool => "what my tool does");