Skip to content

Conversation

@4t145
Copy link
Collaborator

@4t145 4t145 commented Jun 16, 2025

Motivation and Context

Refactor tool macros:

  1. Make it more intuitive and simple, no more attributes on function's parameter.
  2. Better support for generic handler.
  3. No static router.
  4. Composable macros.

How Has This Been Tested?

Breaking Changes

Now we need to use a combination of three macros:

tool

This macro is used to mark a function as a tool handler.

This will generate a function that return the attribute of this tool, with type rmcp::model::Tool.

Usage

feied type usage
name String The name of the tool. If not provided, it defaults to the function name.
description String A description of the tool. The document of this function will be used.
input_schema Expr A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type Parameters<T>
annotations ToolAnnotationsAttribute Additional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
pub async fn my_tool(param: Parameters<MyToolParam>) {
    // handling tool request
}

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.

Usage

feied type usage
router Ident The name of the router function to be generated. Defaults to tool_router.
vis Visibility The visibility of the generated router function. Defaults to empty.

Example

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

    pub fn new() -> Self {
        Self {
            // the default name of tool router will be `tool_router`
            tool_router: Self::tool_router(),
        }
    }
}

Or specify the visibility and router name:

#[tool_router(router = my_tool_router, vis = pub)]
impl MyToolHandler {
    #[tool]
    pub fn my_tool() {
        
    }
}

tool_handler

This macro will generate the handler for tool_call and list_tools methods in the implementation block, by using an existing ToolRouter instance.

Usage

field type usage
router Expr The expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]
impl ServerHandler for MyToolHandler {
    // ...implement other handler
}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
   // ...implement other handler
}

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

This also can be a template for possible prompt handler in the future. And also can be a base of implementation of #[derive(ServerHandler)]

4t145 added 7 commits June 16, 2025 19:21
- 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.
@4t145 4t145 marked this pull request as ready for review June 17, 2025 07:49
@4t145 4t145 requested review from Copilot and jokemanfire and removed request for Copilot June 17, 2025 07:50

This comment was marked as outdated.

@4t145
Copy link
Collaborator Author

4t145 commented Jun 18, 2025

@jokemanfire It's ready for review now

@github-actions github-actions bot added T-documentation Documentation improvements T-dependencies Dependencies related changes T-test Testing related changes T-config Configuration file changes T-core Core library changes T-examples Example code changes T-handler Handler implementation changes T-macros Macro changes labels Jun 18, 2025
@4t145
Copy link
Collaborator Author

4t145 commented Jun 18, 2025

@jokemanfire

We can collect docs like this:

// extract doc line from attribute
fn extract_doc_line(existing_docs: Option<String>, attr: &syn::Attribute) -> Option<String> {
    if !attr.path().is_ident("doc") {
        return None;
    }

    let syn::Meta::NameValue(name_value) = &attr.meta else {
        return None;
    };

    let syn::Expr::Lit(expr_lit) = &name_value.value else {
        return None;
    };

    let syn::Lit::Str(lit_str) = &expr_lit.lit else {
        return None;
    };

    let content = lit_str.value().trim().to_string();
    match (existing_docs, content) {
        (Some(mut existing_docs), content) if !content.is_empty() => {
            existing_docs.push('\n');
            existing_docs.push_str(&content);
            Some(existing_docs)
        }
        (Some(existing_docs), _) => Some(existing_docs),
        (None, content) if !content.is_empty() => Some(content),
        _ => None,
    }
}

and

fn_item.attrs.iter().fold(None, extract_doc_line)

And check again please.

@jokemanfire
Copy link
Collaborator

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like tool_router 's expand is in func 'new' , The struct must have 'ToolRouter', I think it should be write in this readme

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not always, a static router is also ok. I will add more examples.

_request: Option<rmcp::model::PaginatedRequestParam>,
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The marco 'tool_handler' will expand the call_tool and list_tools func , why we need clarify it again?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use #[tool_handler] as much as possible, but remain one place to explain what it looks like after expanded

@jokemanfire
Copy link
Collaborator

The tool_handler marco looks like not really using?

@4t145 4t145 requested review from Copilot and jokemanfire June 23, 2025 04:20
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the tool macros and router implementation to simplify parameter handling, remove attributes on function parameters, and replace the deprecated tool_box macro with the new tool_router and tool_handler macros.

  • Updated macro usage and renaming (tool_box → tool_router/tool_handler).
  • Introduced constructor methods (e.g. Calculator::new()) in multiple examples.
  • Modified function signatures to use a wrapping Parameters type for tool functions.

Reviewed Changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated no comments.

Show a summary per file
File Description
justfile Added basic formatting and lint/fix tasks.
examples/wasi/src/lib.rs Updated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rs Refactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rs Updated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rs Refactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rs Updated tool function signatures and macro usage.
crates/rmcp/tests/* Updated tests to align with new macro signatures.
crates/rmcp/src/{model,handler,handler/server/tool.rs,handler/server/router/tool.rs,handler/server/router.rs,handler/server.rs,README.md} Updated internal macros and API usage throughout the codebase.
crates/rmcp-macros/* Updated macro implementations and documentation for new tools.
Comments suppressed due to low confidence (2)

examples/wasi/src/calculator.rs:50

  • The 'sub' tool now returns a Json-wrapped i32 while 'sum' returns a String. Consider unifying the return types for consistency unless the difference is intentional.
    fn sub(&self, Parameters(SubRequest { a, b }): Parameters<SubRequest>) -> Json<i32> {

examples/servers/src/common/counter.rs:73

  • In the 'echo' function, the JSON object is converted to a string before being wrapped in Content::text. Verify that this conversion is the intended design, as it may limit downstream processing of the original JSON structure.
    fn echo(&self, Parameters(object): Parameters<JsonObject>) -> Result<CallToolResult, McpError> {

@4t145
Copy link
Collaborator Author

4t145 commented Jun 23, 2025

@jokemanfire check again please

@4t145 4t145 merged commit 1f7f4d3 into modelcontextprotocol:main Jun 23, 2025
10 of 11 checks passed
@github-actions github-actions bot mentioned this pull request Jul 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-documentation Documentation improvements T-examples Example code changes T-handler Handler implementation changes T-macros Macro changes T-test Testing related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants