refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

refactor: refactor tool macros and router implementation - #261

Merged
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro
Jun 23, 2025
Merged

refactor: refactor tool macros and router implementation#261
4t145 merged 13 commits into
modelcontextprotocol:mainfrom
4t145:refactor-macro

Conversation

@4t145

@4t1454t145 commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

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

feiedtypeusage
nameStringThe name of the tool. If not provided, it defaults to the function name.
descriptionStringA description of the tool. The document of this function will be used.
input_schemaExprA 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>
annotationsToolAnnotationsAttributeAdditional tool information. Defaults to None.

Example

#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]pubasyncfnmy_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

feiedtypeusage
routerIdentThe name of the router function to be generated. Defaults to tool_router.
visVisibilityThe visibility of the generated router function. Defaults to empty.

Example

#[tool_router]implMyToolHandler{#[tool]pubfnmy_tool(){}pubfnnew() -> 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)]implMyToolHandler{#[tool]pubfnmy_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

fieldtypeusage
routerExprThe expression to access the ToolRouter instance. Defaults to self.tool_router.

Example

#[tool_handler]implServerHandlerforMyToolHandler{// ...implement other handler}

or using a custom router expression:

#[tool_handler(router = self.get_router().await)]implServerHandlerforMyToolHandler{// ...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 CopilotJune 17, 2025 07:50

This comment was marked as outdated.

@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire It's ready for review now

@github-actionsgithub-actionsBot 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
Comment threadcrates/rmcp-macros/src/tool.rs Outdated
Comment threadcrates/rmcp/src/handler/server.rs Outdated
@4t145

Copy link
Copy Markdown
ContributorAuthor

@jokemanfire

We can collect docs like this:

// extract doc line from attributefnextract_doc_line(existing_docs:Option<String>,attr:&syn::Attribute) -> Option<String>{if !attr.path().is_ident("doc"){returnNone;}let syn::Meta::NameValue(name_value) = &attr.metaelse{returnNone;};let syn::Expr::Lit(expr_lit) = &name_value.valueelse{returnNone;};let syn::Lit::Str(lit_str) = &expr_lit.litelse{returnNone;};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
Copy Markdown
Member

I will give a feedback tonight.


```rust ignore
#[tool(tool_box)]
#[tool_router]

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

The tool_handler marco looks like not really using?

@4t145
4t145 requested review from Copilot and jokemanfireJune 23, 2025 04:20

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
justfileAdded basic formatting and lint/fix tasks.
examples/wasi/src/lib.rsUpdated Calculator instantiation using Calculator::new().
examples/wasi/src/calculator.rsRefactored Calculator, updated parameter extraction and return types.
examples/transport/src/{websocket,unix_socket,tcp,http_upgrade}.rsUpdated server instantiations to use Calculator::new().
examples/transport/src/common/calculator.rsRefactored calculator module with new macros and added capabilities.
examples/servers/src/common/{generic_service,counter,calculator}.rsUpdated 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
Copy Markdown
ContributorAuthor

@jokemanfire check again please

@4t145
4t145 merged commit 1f7f4d3 into modelcontextprotocol:mainJun 23, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…tprotocol#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-documentationDocumentation improvementsT-examplesExample code changesT-handlerHandler implementation changesT-macrosMacro changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@4t145@jokemanfire