Skip to content

Commit 3ab5fe7

Browse files
authored
feat: add elicitation macros and add elicit_input() method (#99)
* Add Streamable HTTP Client and multiple refactoring and improvements * chore: typos * chore: update readme * feat: introduce event-store * chore: add event store to the app state * chore: refactor event store integration * chore: add tracing to inmemory store * chore: update examples to use event store * chore: improve flow * chore: replay mechanism * cleanup * test: add new test for event-store * chore: add tracing to tests * chore: add test * chore: refactor replaying logic * chore: cleanup * feat: add elicit_input to the McpServer * chore: enhance jsonschema macro * feat: introduce mcp_elicit macro * feat: add default, minimu, macimum support * improve tests and enum support * implement from_content_map * update docs * update readme * cleanup * fix: tests * update to latest rust-mcp-schema * chore: issues * chore: update readme * update readme * chore: typo
1 parent 08742bb commit 3ab5fe7

File tree

14 files changed

+1609
-238
lines changed

14 files changed

+1609
-238
lines changed

Cargo.lock

Lines changed: 96 additions & 139 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ This project supports following transports:
3838
- ✅ Batch Messages
3939
- ✅ Streaming & non-streaming JSON response
4040
- ✅ Streamable HTTP Support for MCP Clients
41-
- Resumability
42-
-Authentication / Oauth
41+
- Resumability
42+
- ⬜ Oauth Authentication
4343

4444
**⚠️** Project is currently under development and should be used at your own risk.
4545

@@ -50,6 +50,7 @@ This project supports following transports:
5050
- [MCP Client (stdio)](#mcp-client-stdio)
5151
- [MCP Client (Streamable HTTP)](#mcp-client_streamable-http))
5252
- [MCP Client (sse)](#mcp-client-sse)
53+
- [Macros](#macros)
5354
- [Getting Started](#getting-started)
5455
- [HyperServerOptions](#hyperserveroptions)
5556
- [Security Considerations](#security-considerations)
@@ -386,6 +387,114 @@ Creating an MCP client using the `rust-mcp-sdk` with the SSE transport is almost
386387
👉 see [examples/simple-mcp-client-sse](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-sse) for a complete working example.
387388

388389

390+
## Macros
391+
[rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) includes several helpful macros that simplify common tasks when building MCP servers and clients. For example, they can automatically generate tool specifications and tool schemas right from your structs, or assist with elicitation requests and responses making them completely type safe.
392+
393+
> To use these macros, ensure the `macros` feature is enabled in your Cargo.toml.
394+
395+
### mcp_tool
396+
`mcp_tool` is a procedural macro attribute that helps generating rust_mcp_schema::Tool from a struct.
397+
398+
Usage example:
399+
```rust
400+
#[mcp_tool(
401+
name = "move_file",
402+
title="Move File",
403+
description = concat!("Move or rename files and directories. Can move files between directories ",
404+
"and rename them in a single operation. If the destination exists, the ",
405+
"operation will fail. Works across different directories and can be used ",
406+
"for simple renaming within the same directory. ",
407+
"Both source and destination must be within allowed directories."),
408+
destructive_hint = false,
409+
idempotent_hint = false,
410+
open_world_hint = false,
411+
read_only_hint = false
412+
)]
413+
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, JsonSchema)]
414+
pub struct MoveFileTool {
415+
/// The source path of the file to move.
416+
pub source: String,
417+
/// The destination path to move the file to.
418+
pub destination: String,
419+
}
420+
421+
// Now we can call `tool()` method on it to get a Tool instance
422+
let rust_mcp_sdk::schema::Tool = MoveFileTool::tool();
423+
424+
```
425+
426+
💻 For a real-world example, check out any of the tools available at: https://github.com/rust-mcp-stack/rust-mcp-filesystem/tree/main/src/tools
427+
428+
429+
### tool_box
430+
`tool_box` generates an enum from a provided list of tools, making it easier to organize and manage them, especially when your application includes a large number of tools.
431+
432+
It accepts an array of tools and generates an enum where each tool becomes a variant of the enum.
433+
434+
Generated enum has a `tools()` function that returns a `Vec<Tool>` , and a `TryFrom<CallToolRequestParams>` trait implementation that could be used to convert a ToolRequest into a Tool instance.
435+
436+
Usage example:
437+
```rust
438+
// Accepts an array of tools and generates an enum named `FileSystemTools`,
439+
// where each tool becomes a variant of the enum.
440+
tool_box!(FileSystemTools, [ReadFileTool, MoveFileTool, SearchFilesTool]);
441+
442+
// now in the app, we can use the FileSystemTools, like:
443+
let all_tools: Vec<Tool> = FileSystemTools::tools();
444+
```
445+
446+
💻 To see a real-world example of that please see :
447+
- `tool_box` macro usage: [https://github.com/rust-mcp-stack/rust-mcp-filesystem/blob/main/src/tools.rs](https://github.com/rust-mcp-stack/rust-mcp-filesystem/blob/main/src/tools.rs)
448+
- using `tools()` in list tools request : [https://github.com/rust-mcp-stack/rust-mcp-filesystem/blob/main/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-filesystem/blob/main/src/handler.rs#L67)
449+
- using `try_from` in call tool_request: [https://github.com/rust-mcp-stack/rust-mcp-filesystem/blob/main/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-filesystem/blob/main/src/handler.rs#L100)
450+
451+
452+
453+
### mcp_elicit
454+
The `mcp_elicit` macro generates implementations for the annotated struct to facilitate data elicitation. It enables struct to generate `ElicitRequestedSchema` and also parsing a map of field names to `ElicitResultContentValue` values back into the struct, supporting both required and optional fields. The generated implementation includes:
455+
456+
- A `message()` method returning the elicitation message as a string.
457+
- A `requested_schema()` method returning an `ElicitRequestedSchema` based on the struct’s JSON schema.
458+
- A `from_content_map()` method to convert a map of `ElicitResultContentValue` values into a struct instance.
459+
460+
### Attributes
461+
462+
- `message` - An optional string (or `concat!(...)` expression) to prompt the user or system for input. Defaults to an empty string if not provided.
463+
464+
Usage example:
465+
```rust
466+
// A struct that could be used to send elicit request and get the input from the user
467+
#[mcp_elicit(message = "Please enter your info")]
468+
#[derive(JsonSchema)]
469+
pub struct UserInfo {
470+
#[json_schema(
471+
title = "Name",
472+
description = "The user's full name",
473+
min_length = 5,
474+
max_length = 100
475+
)]
476+
pub name: String,
477+
/// Is user a student?
478+
#[json_schema(title = "Is student?", default = true)]
479+
pub is_student: Option<bool>,
480+
481+
/// User's favorite color
482+
pub favorate_color: Colors,
483+
}
484+
485+
// send a Elicit Request , ask for UserInfo data and convert the result back to a valid UserInfo instance
486+
let result: ElicitResult = server
487+
.elicit_input(UserInfo::message(), UserInfo::requested_schema())
488+
.await?;
489+
490+
// Create a UserInfo instance using data provided by the user on the client side
491+
let user_info = UserInfo::from_content_map(result.content)?;
492+
493+
```
494+
495+
💻 For mre info please see :
496+
- https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/crates/rust-mcp-macros
497+
389498
## Getting Started
390499

391500
If you are looking for a step-by-step tutorial on how to get started with `rust-mcp-sdk` , please see : [Getting Started MCP Server](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/doc/getting-started-mcp-server.md)
@@ -509,6 +618,7 @@ The `rust-mcp-sdk` crate provides several features that can be enabled or disabl
509618
- `stdio`: Enables support for the `standard input/output (stdio)` transport.
510619
- `tls-no-provider`: Enables TLS without a crypto provider. This is useful if you are already using a different crypto provider than the aws-lc default.
511620

621+
512622
#### MCP Protocol Versions with Corresponding Features
513623

514624
- `2025_06_18` : Activates MCP Protocol version 2025-06-18 (enabled by default)
@@ -621,6 +731,10 @@ Below is a list of projects that utilize the `rust-mcp-sdk`, showcasing their na
621731

622732

623733

734+
735+
736+
737+
624738
## Contributing
625739

626740
We welcome everyone who wishes to contribute! Please refer to the [contributing](CONTRIBUTING.md) guidelines for more details.

crates/rust-mcp-macros/README.md

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# rust-mcp-macros.
22

3+
4+
## mcp_tool Macro
5+
36
A procedural macro, part of the [rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) ecosystem, to generate `rust_mcp_schema::Tool` instance from a struct.
47

58
The `mcp_tool` macro generates an implementation for the annotated struct that includes:
@@ -80,11 +83,7 @@ fn main() {
8083

8184
```
8285

83-
---
8486

85-
<img align="top" src="assets/rust-mcp-stack-icon.png" width="24" style="border-radius:0.2rem;"> Check out [rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) , a high-performance, asynchronous toolkit for building MCP servers and clients. Focus on your app's logic while [rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) takes care of the rest!
86-
87-
---
8887

8988

9089
**Note**: The following attributes are available only in version `2025_03_26` and later of the MCP Schema, and their values will be used in the [annotations](https://github.com/rust-mcp-stack/rust-mcp-schema/blob/main/src/generated_schema/2025_03_26/mcp_schema.rs#L5557) attribute of the *[Tool struct](https://github.com/rust-mcp-stack/rust-mcp-schema/blob/main/src/generated_schema/2025_03_26/mcp_schema.rs#L5554-L5566).
@@ -93,3 +92,106 @@ fn main() {
9392
- `idempotent_hint`
9493
- `open_world_hint`
9594
- `read_only_hint`
95+
96+
97+
98+
99+
100+
## mcp_elicit Macro
101+
102+
The `mcp_elicit` macro generates implementations for the annotated struct to facilitate data elicitation. It enables struct to generate `ElicitRequestedSchema` and also parsing a map of field names to `ElicitResultContentValue` values back into the struct, supporting both required and optional fields. The generated implementation includes:
103+
104+
- A `message()` method returning the elicitation message as a string.
105+
- A `requested_schema()` method returning an `ElicitRequestedSchema` based on the struct’s JSON schema.
106+
- A `from_content_map()` method to convert a map of `ElicitResultContentValue` values into a struct instance.
107+
108+
### Attributes
109+
110+
- `message` - An optional string (or `concat!(...)` expression) to prompt the user or system for input. Defaults to an empty string if not provided.
111+
112+
### Supported Field Types
113+
114+
- `String`: Maps to `ElicitResultContentValue::String`.
115+
- `bool`: Maps to `ElicitResultContentValue::Boolean`.
116+
- `i32`: Maps to `ElicitResultContentValue::Integer` (with bounds checking).
117+
- `i64`: Maps to `ElicitResultContentValue::Integer`.
118+
- `enum` Only simple enums are supported. The enum must implement the FromStr trait.
119+
- `Option<T>`: Supported for any of the above types, mapping to `None` if the field is missing.
120+
121+
122+
### Usage Example
123+
124+
```rust
125+
use rust_mcp_sdk::macros::{mcp_elicit, JsonSchema};
126+
use rust_mcp_sdk::schema::RpcError;
127+
use std::str::FromStr;
128+
129+
// Simple enum with FromStr trait implemented
130+
#[derive(JsonSchema, Debug)]
131+
pub enum Colors {
132+
#[json_schema(title = "Green Color")]
133+
Green,
134+
#[json_schema(title = "Red Color")]
135+
Red,
136+
}
137+
impl FromStr for Colors {
138+
type Err = RpcError;
139+
140+
fn from_str(s: &str) -> Result<Self, Self::Err> {
141+
match s.to_lowercase().as_str() {
142+
"green" => Ok(Colors::Green),
143+
"red" => Ok(Colors::Red),
144+
_ => Err(RpcError::parse_error().with_message("Invalid color".to_string())),
145+
}
146+
}
147+
}
148+
149+
// A struct that could be used to send elicit request and get the input from the user
150+
#[mcp_elicit(message = "Please enter your info")]
151+
#[derive(JsonSchema)]
152+
pub struct UserInfo {
153+
#[json_schema(
154+
title = "Name",
155+
description = "The user's full name",
156+
min_length = 5,
157+
max_length = 100
158+
)]
159+
pub name: String,
160+
161+
/// Email address of the user
162+
#[json_schema(title = "Email", format = "email")]
163+
pub email: Option<String>,
164+
165+
/// The user's age in years
166+
#[json_schema(title = "Age", minimum = 15, maximum = 125)]
167+
pub age: i32,
168+
169+
/// Is user a student?
170+
#[json_schema(title = "Is student?", default = true)]
171+
pub is_student: Option<bool>,
172+
173+
/// User's favorite color
174+
pub favorate_color: Colors,
175+
}
176+
177+
// ....
178+
// .......
179+
// ...........
180+
181+
// send a Elicit Request , ask for UserInfo data and convert the result back to a valid UserInfo instance
182+
183+
let result: ElicitResult = server
184+
.elicit_input(UserInfo::message(), UserInfo::requested_schema())
185+
.await?;
186+
187+
// Create a UserInfo instance using data provided by the user on the client side
188+
let user_info = UserInfo::from_content_map(result.content)?;
189+
190+
191+
```
192+
193+
---
194+
195+
<img align="top" src="assets/rust-mcp-stack-icon.png" width="24" style="border-radius:0.2rem;"> Check out [rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk), a high-performance, asynchronous toolkit for building MCP servers and clients. Focus on your app's logic while [rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) takes care of the rest!
196+
197+
---

0 commit comments

Comments
 (0)