Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,5 +124,10 @@ See [oauth_support](docs/OAUTH_SUPPORT.md) for details.
## Related Projects
- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
## Development

### Tips for Contributors
See [docs/CONTRIBUTE.MD](docs/CONTRIBUTE.MD) to get some tips for contributing.

### Using Dev Container
If you want to use dev container, see [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
2 changes: 1 addition & 1 deletion crates/rmcp-macros/Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ syn = {version = "2", features = ["full"]}
quote = "1"
proc-macro2 = "1"
serde_json = "1.0"

darling = { version = "0.20" }

[features]
[dev-dependencies]
146 changes: 127 additions & 19 deletions crates/rmcp-macros/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,41 +10,149 @@ This library primarily provides the following macros:

## Usage

### Tool Macro
### tool

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

```rust ignore
#[tool]
fn calculator(&self, #[tool(param)] a: i32, #[tool(param)] b: i32) -> Result<CallToolResult, Error> {
// Implement tool functionality
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.

#### Usage

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

#### Example

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

Use on an impl block to automatically register multiple tools:
### 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.

In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.

#### Usage

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

```rust ignore
#[tool(tool_box)]
impl MyHandler {
#### Example

```rust
#[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.

impl MyToolHandler {
#[tool]
fn tool1(&self) -> Result<CallToolResult, Error> {
// Tool 1 implementation
pub fn my_tool() {

}

#[tool]
fn tool2(&self) -> Result<CallToolResult, Error> {
// Tool 2 implementation

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

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

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

}
}
}

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

}
}
}

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


### tool_handler

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

#### Usage

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

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

or using a custom router expression:
```rust
#[tool_handler(router = self.get_router().await)]
impl ServerHandler for MyToolHandler {
// ...implement other handler
}
```

#### Explained
This macro will be expended to something like this:
```rust
impl ServerHandler for MyToolHandler {
async fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}

async fn list_tools(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}
}
```


## Advanced Features

- Support for parameter aggregation (`#[tool(aggr)]`)
- Support for custom tool names and descriptions
- Automatic generation of tool descriptions from documentation comments
- JSON Schema generation for tool parameters
Expand Down
153 changes: 152 additions & 1 deletion crates/rmcp-macros/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,161 @@
use proc_macro::TokenStream;

mod tool;

mod tool_handler;
mod tool_router;
/// # tool
///
/// This macro is used to mark a function as a tool handler.
///
/// This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`.
///
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `name` | `String` | The name of the tool. If not provided, it defaults to the function name. |
/// | `description` | `String` | A description of the tool. The document of this function will be used. |
/// | `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters<T>` |
/// | `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))]
/// pub async fn my_tool(param: Parameters<MyToolParam>) {
/// // handling tool request
/// }
/// ```
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
tool::tool(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # 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.
///
/// In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable.
/// ## Usage
///
/// | feied | type | usage |
/// | :- | :- | :- |
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
///
/// ## Example
///
/// ```rust,ignore
/// #[tool_router]
/// impl MyToolHandler {
/// #[tool]
/// pub fn my_tool() {
///
/// }
///
/// pub fn new() -> Self {
/// Self {
/// // the default name of tool router will be `tool_router`
/// tool_router: Self::tool_router(),
/// }
/// }
/// }
/// ```
///
/// Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one:
///
/// ```rust,ignore
/// mod a {
/// #[tool_router(router = tool_router_a, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_a() {
///
/// }
/// }
/// }
///
/// mod b {
/// #[tool_router(router = tool_router_b, vis = pub)]
/// impl MyToolHandler {
/// #[tool]
/// fn my_tool_b() {
///
/// }
/// }
/// }
///
/// impl MyToolHandler {
/// fn new() -> Self {
/// Self {
/// tool_router: self::tool_router_a() + self::tool_router_b(),
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// # tool_handler
///
/// This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance.
///
/// ## Usage
///
/// | field | type | usage |
/// | :- | :- | :- |
/// | `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. |
/// ## Example
/// ```rust,ignore
/// #[tool_handler]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// or using a custom router expression:
/// ```rust,ignore
/// #[tool_handler(router = self.get_router().await)]
/// impl ServerHandler for MyToolHandler {
/// // ...implement other handler
/// }
/// ```
///
/// ## Explain
///
/// This macro will be expended to something like this:
/// ```rust,ignore
/// impl ServerHandler for MyToolHandler {
/// async fn call_tool(
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
///
/// async fn list_tools(
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tool_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_handler::tool_handler(attr.into(), input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Loading