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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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

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 number Diff line number Diff 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
Loading