Skip to content

Commit c4009fb

Browse files
committed
feat: add builder methods to Tool struct for setting schemas
- Add with_output_schema<T>() method to set output schema from type - Add with_input_schema<T>() method to set input schema from type - Both methods use cached_schema_for_type internally - Add comprehensive tests for builder methods
1 parent 6867154 commit c4009fb

File tree

4 files changed

+106
-17
lines changed

4 files changed

+106
-17
lines changed

TODO.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ Implement changes based on PR review feedback from https://github.com/modelconte
2424
- [x] Test macro with various return type configurations
2525

2626
### Phase 4: Add builder methods to Tool struct
27-
- [ ] Add `with_output_schema<T: JsonSchema>()` method to Tool struct
28-
- [ ] Add `with_input_schema<T: JsonSchema>()` method to Tool struct
29-
- [ ] Update documentation for these methods
27+
- [x] Add `with_output_schema<T: JsonSchema>()` method to Tool struct
28+
- [x] Add `with_input_schema<T: JsonSchema>()` method to Tool struct
29+
- [x] Update documentation for these methods
3030

3131
### Phase 5: Update examples and tests
32-
- [ ] Update `examples/servers/src/structured_output.rs` to use `Json<T>`
33-
- [ ] Update `crates/rmcp/tests/test_structured_output.rs` to use `Json<T>`
34-
- [ ] Update any other examples or tests using `Structured<T>`
35-
- [ ] Update documentation in `crates/rmcp/src/handler/server/tool.rs`
32+
- [x] Update `examples/servers/src/structured_output.rs` to use `Json<T>` (done in Phase 1)
33+
- [x] Update `crates/rmcp/tests/test_structured_output.rs` to use `Json<T>` (done in Phase 1)
34+
- [x] Update any other examples or tests using `Structured<T>` (done in Phase 1)
35+
- [x] Update documentation in `crates/rmcp/src/handler/server/tool.rs` (done in Phase 1)
3636

3737
### Phase 6: Update validation logic
3838
- [ ] Modify validation in `crates/rmcp/src/handler/server/router/tool.rs` to use trait-based schema

crates/rmcp/src/model/tool.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{borrow::Cow, sync::Arc};
22

3+
use schemars::JsonSchema;
34
/// Tools represent a routine that a server can execute
45
/// Tool calls represent requests from the client to execute one
56
use serde::{Deserialize, Serialize};
@@ -151,6 +152,18 @@ impl Tool {
151152
}
152153
}
153154

155+
/// Set the output schema using a type that implements JsonSchema
156+
pub fn with_output_schema<T: JsonSchema + 'static>(mut self) -> Self {
157+
self.output_schema = Some(crate::handler::server::tool::cached_schema_for_type::<T>());
158+
self
159+
}
160+
161+
/// Set the input schema using a type that implements JsonSchema
162+
pub fn with_input_schema<T: JsonSchema + 'static>(mut self) -> Self {
163+
self.input_schema = crate::handler::server::tool::cached_schema_for_type::<T>();
164+
self
165+
}
166+
154167
/// Get the schema as json value
155168
pub fn schema_as_json_value(&self) -> Value {
156169
Value::Object(self.input_schema.as_ref().clone())

crates/rmcp/tests/test_json_schema_detection.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
//cargo test --test test_json_schema_detection --features "client server macros"
22
use rmcp::{
3-
Json, ServerHandler,
4-
handler::server::router::tool::ToolRouter,
5-
tool, tool_handler, tool_router,
3+
Json, ServerHandler, handler::server::router::tool::ToolRouter, tool, tool_handler, tool_router,
64
};
75
use schemars::JsonSchema;
86
use serde::{Deserialize, Serialize};
@@ -37,7 +35,9 @@ impl TestServer {
3735
/// Tool that returns Json<T> - should have output schema
3836
#[tool(name = "with-json")]
3937
pub async fn with_json(&self) -> Result<Json<TestData>, String> {
40-
Ok(Json(TestData { value: "test".to_string() }))
38+
Ok(Json(TestData {
39+
value: "test".to_string(),
40+
}))
4141
}
4242

4343
/// Tool that returns regular type - should NOT have output schema
@@ -49,7 +49,9 @@ impl TestServer {
4949
/// Tool that returns Result with inner Json - should have output schema
5050
#[tool(name = "result-with-json")]
5151
pub async fn result_with_json(&self) -> Result<Json<TestData>, rmcp::ErrorData> {
52-
Ok(Json(TestData { value: "test".to_string() }))
52+
Ok(Json(TestData {
53+
value: "test".to_string(),
54+
}))
5355
}
5456

5557
/// Tool with explicit output_schema attribute - should have output schema
@@ -66,7 +68,10 @@ async fn test_json_type_generates_schema() {
6668

6769
// Find the with-json tool
6870
let json_tool = tools.iter().find(|t| t.name == "with-json").unwrap();
69-
assert!(json_tool.output_schema.is_some(), "Json<T> return type should generate output schema");
71+
assert!(
72+
json_tool.output_schema.is_some(),
73+
"Json<T> return type should generate output schema"
74+
);
7075
}
7176

7277
#[tokio::test]
@@ -76,7 +81,10 @@ async fn test_non_json_type_no_schema() {
7681

7782
// Find the without-json tool
7883
let non_json_tool = tools.iter().find(|t| t.name == "without-json").unwrap();
79-
assert!(non_json_tool.output_schema.is_none(), "Regular return type should NOT generate output schema");
84+
assert!(
85+
non_json_tool.output_schema.is_none(),
86+
"Regular return type should NOT generate output schema"
87+
);
8088
}
8189

8290
#[tokio::test]
@@ -86,7 +94,10 @@ async fn test_result_with_json_generates_schema() {
8694

8795
// Find the result-with-json tool
8896
let result_json_tool = tools.iter().find(|t| t.name == "result-with-json").unwrap();
89-
assert!(result_json_tool.output_schema.is_some(), "Result<Json<T>, E> return type should generate output schema");
97+
assert!(
98+
result_json_tool.output_schema.is_some(),
99+
"Result<Json<T>, E> return type should generate output schema"
100+
);
90101
}
91102

92103
#[tokio::test]
@@ -96,5 +107,8 @@ async fn test_explicit_schema_override() {
96107

97108
// Find the explicit-schema tool
98109
let explicit_tool = tools.iter().find(|t| t.name == "explicit-schema").unwrap();
99-
assert!(explicit_tool.output_schema.is_some(), "Explicit output_schema attribute should work");
100-
}
110+
assert!(
111+
explicit_tool.output_schema.is_some(),
112+
"Explicit output_schema attribute should work"
113+
);
114+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//cargo test --test test_tool_builder_methods --features "client server macros"
2+
use rmcp::model::{JsonObject, Tool};
3+
use schemars::JsonSchema;
4+
use serde::{Deserialize, Serialize};
5+
6+
#[derive(Serialize, Deserialize, JsonSchema)]
7+
pub struct InputData {
8+
pub name: String,
9+
pub age: u32,
10+
}
11+
12+
#[derive(Serialize, Deserialize, JsonSchema)]
13+
pub struct OutputData {
14+
pub greeting: String,
15+
pub is_adult: bool,
16+
}
17+
18+
#[test]
19+
fn test_with_output_schema() {
20+
let tool = Tool::new("test", "Test tool", JsonObject::new()).with_output_schema::<OutputData>();
21+
22+
assert!(tool.output_schema.is_some());
23+
24+
// Verify the schema contains expected fields
25+
let schema_str = serde_json::to_string(tool.output_schema.as_ref().unwrap()).unwrap();
26+
assert!(schema_str.contains("greeting"));
27+
assert!(schema_str.contains("is_adult"));
28+
}
29+
30+
#[test]
31+
fn test_with_input_schema() {
32+
let tool = Tool::new("test", "Test tool", JsonObject::new()).with_input_schema::<InputData>();
33+
34+
// Verify the schema contains expected fields
35+
let schema_str = serde_json::to_string(&tool.input_schema).unwrap();
36+
assert!(schema_str.contains("name"));
37+
assert!(schema_str.contains("age"));
38+
}
39+
40+
#[test]
41+
fn test_chained_builder_methods() {
42+
let tool = Tool::new("test", "Test tool", JsonObject::new())
43+
.with_input_schema::<InputData>()
44+
.with_output_schema::<OutputData>()
45+
.annotate(rmcp::model::ToolAnnotations::new().read_only(true));
46+
47+
assert!(tool.output_schema.is_some());
48+
assert!(tool.annotations.is_some());
49+
assert_eq!(
50+
tool.annotations.as_ref().unwrap().read_only_hint,
51+
Some(true)
52+
);
53+
54+
// Verify both schemas are set correctly
55+
let input_schema_str = serde_json::to_string(&tool.input_schema).unwrap();
56+
assert!(input_schema_str.contains("name"));
57+
assert!(input_schema_str.contains("age"));
58+
59+
let output_schema_str = serde_json::to_string(tool.output_schema.as_ref().unwrap()).unwrap();
60+
assert!(output_schema_str.contains("greeting"));
61+
assert!(output_schema_str.contains("is_adult"));
62+
}

0 commit comments

Comments
 (0)