Skip to content

Commit 2d90b76

Browse files
authored
fix: api ergonomics follow-up (#720)
* fix: builder with_* methods take T instead of Option<T> * fix: emit conditional builder calls for optional fields in macros * fix: convert with_task, with_stop_reason, with_logger, with_content to proper builders * fix: update test callers for new builder signatures * fix: simplify make_task helper and remove unused import * fix: update sampling_stdio example for new with_stop_reason signature * fix: make annotations and execution Option<Expr> consistent with other fields * fix: remove unused none_expr import
1 parent 1fe5d1e commit 2d90b76

File tree

9 files changed

+81
-99
lines changed

9 files changed

+81
-99
lines changed

crates/rmcp-macros/src/prompt.rs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,31 +46,23 @@ impl ResolvedPromptAttribute {
4646
} else {
4747
quote! { None::<String> }
4848
};
49-
let title = if let Some(title) = title {
50-
quote! { Some(#title.into()) }
51-
} else {
52-
quote! { None }
53-
};
54-
let icons = if let Some(icons) = icons {
55-
quote! { Some(#icons) }
56-
} else {
57-
quote! { None }
58-
};
59-
let meta = if let Some(meta) = meta {
60-
quote! { Some(#meta) }
61-
} else {
62-
quote! { None }
63-
};
49+
let title_call = title
50+
.map(|t| quote! { .with_title(#t) })
51+
.unwrap_or_default();
52+
let icons_call = icons
53+
.map(|i| quote! { .with_icons(#i) })
54+
.unwrap_or_default();
55+
let meta_call = meta.map(|m| quote! { .with_meta(#m) }).unwrap_or_default();
6456
let tokens = quote! {
6557
pub fn #fn_ident() -> rmcp::model::Prompt {
6658
rmcp::model::Prompt::from_raw(
6759
#name,
6860
#description,
6961
#arguments,
7062
)
71-
.with_title(#title)
72-
.with_icons(#icons)
73-
.with_meta(#meta)
63+
#title_call
64+
#icons_call
65+
#meta_call
7466
}
7567
};
7668
syn::parse2::<ImplItemFn>(tokens)

crates/rmcp-macros/src/tool.rs

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use proc_macro2::{Span, TokenStream};
33
use quote::{ToTokens, format_ident, quote};
44
use syn::{Expr, Ident, ImplItemFn, LitStr, ReturnType, parse_quote};
55

6-
use crate::common::{extract_doc_line, none_expr};
6+
use crate::common::extract_doc_line;
77

88
/// Check if a type is Json<T> and extract the inner type T
99
fn extract_json_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
@@ -110,8 +110,8 @@ pub struct ResolvedToolAttribute {
110110
pub description: Option<Expr>,
111111
pub input_schema: Expr,
112112
pub output_schema: Option<Expr>,
113-
pub annotations: Expr,
114-
pub execution: Expr,
113+
pub annotations: Option<Expr>,
114+
pub execution: Option<Expr>,
115115
pub icons: Option<Expr>,
116116
pub meta: Option<Expr>,
117117
}
@@ -134,26 +134,22 @@ impl ResolvedToolAttribute {
134134
} else {
135135
quote! { None }
136136
};
137-
let output_schema = if let Some(output_schema) = output_schema {
138-
quote! { Some(#output_schema) }
139-
} else {
140-
quote! { None }
141-
};
142-
let title = if let Some(title) = title {
143-
quote! { Some(#title.into()) }
144-
} else {
145-
quote! { None }
146-
};
147-
let icons = if let Some(icons) = icons {
148-
quote! { Some(#icons) }
149-
} else {
150-
quote! { None }
151-
};
152-
let meta = if let Some(meta) = meta {
153-
quote! { Some(#meta) }
154-
} else {
155-
quote! { None }
156-
};
137+
let title_call = title
138+
.map(|t| quote! { .with_title(#t) })
139+
.unwrap_or_default();
140+
let output_schema_call = output_schema
141+
.map(|s| quote! { .with_raw_output_schema(#s) })
142+
.unwrap_or_default();
143+
let annotations_call = annotations
144+
.map(|a| quote! { .with_annotations(#a) })
145+
.unwrap_or_default();
146+
let execution_call = execution
147+
.map(|e| quote! { .with_execution(#e) })
148+
.unwrap_or_default();
149+
let icons_call = icons
150+
.map(|i| quote! { .with_icons(#i) })
151+
.unwrap_or_default();
152+
let meta_call = meta.map(|m| quote! { .with_meta(#m) }).unwrap_or_default();
157153
let doc_comment = format!("Generated tool metadata function for {name}");
158154
let doc_attr: syn::Attribute = parse_quote!(#[doc = #doc_comment]);
159155
let tokens = quote! {
@@ -164,12 +160,12 @@ impl ResolvedToolAttribute {
164160
#description,
165161
#input_schema,
166162
)
167-
.with_title(#title)
168-
.with_raw_output_schema(#output_schema)
169-
.with_annotations(#annotations)
170-
.with_execution(#execution)
171-
.with_icons(#icons)
172-
.with_meta(#meta)
163+
#title_call
164+
#output_schema_call
165+
#annotations_call
166+
#execution_call
167+
#icons_call
168+
#meta_call
173169
}
174170
};
175171
syn::parse2::<ImplItemFn>(tokens)
@@ -260,17 +256,17 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
260256
let idempotent_hint = wrap_option(idempotent_hint);
261257
let open_world_hint = wrap_option(open_world_hint);
262258
let token_stream = quote! {
263-
Some(rmcp::model::ToolAnnotations::from_raw(
259+
rmcp::model::ToolAnnotations::from_raw(
264260
#title,
265261
#read_only_hint,
266262
#destructive_hint,
267263
#idempotent_hint,
268264
#open_world_hint,
269-
))
265+
)
270266
};
271-
syn::parse2::<Expr>(token_stream)?
267+
Some(syn::parse2::<Expr>(token_stream)?)
272268
} else {
273-
none_expr()?
269+
None
274270
};
275271
let execution_expr = if let Some(execution) = attribute.execution {
276272
let ToolExecutionAttribute { task_support } = execution;
@@ -296,13 +292,13 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
296292
};
297293

298294
let token_stream = quote! {
299-
Some(rmcp::model::ToolExecution::from_raw(
295+
rmcp::model::ToolExecution::from_raw(
300296
#task_support_expr,
301-
))
297+
)
302298
};
303-
syn::parse2::<Expr>(token_stream)?
299+
Some(syn::parse2::<Expr>(token_stream)?)
304300
} else {
305-
none_expr()?
301+
None
306302
};
307303
// Handle output_schema - either explicit or generated from return type
308304
let output_schema_expr = attribute.output_schema.or_else(|| {

crates/rmcp/src/model.rs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,13 +1455,10 @@ impl LoggingMessageNotificationParam {
14551455
}
14561456
}
14571457

1458-
/// Create with a logger name.
1459-
pub fn with_logger(level: LoggingLevel, logger: impl Into<String>, data: Value) -> Self {
1460-
Self {
1461-
level,
1462-
logger: Some(logger.into()),
1463-
data,
1464-
}
1458+
/// Set the logger name.
1459+
pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
1460+
self.logger = Some(logger.into());
1461+
self
14651462
}
14661463
}
14671464

@@ -2605,12 +2602,10 @@ impl CreateElicitationResult {
26052602
}
26062603
}
26072604

2608-
/// Create with content.
2609-
pub fn with_content(action: ElicitationAction, content: Value) -> Self {
2610-
Self {
2611-
action,
2612-
content: Some(content),
2613-
}
2605+
/// Set the content on this result.
2606+
pub fn with_content(mut self, content: Value) -> Self {
2607+
self.content = Some(content);
2608+
self
26142609
}
26152610
}
26162611

@@ -2822,8 +2817,8 @@ impl CallToolRequestParams {
28222817
}
28232818

28242819
/// Sets the task metadata for this tool call.
2825-
pub fn with_task(mut self, task: Option<JsonObject>) -> Self {
2826-
self.task = task;
2820+
pub fn with_task(mut self, task: JsonObject) -> Self {
2821+
self.task = Some(task);
28272822
self
28282823
}
28292824
}
@@ -2889,8 +2884,8 @@ impl CreateMessageResult {
28892884
pub const STOP_REASON_TOOL_USE: &str = "toolUse";
28902885

28912886
/// Set the stop reason.
2892-
pub fn with_stop_reason(mut self, stop_reason: Option<String>) -> Self {
2893-
self.stop_reason = stop_reason;
2887+
pub fn with_stop_reason(mut self, stop_reason: impl Into<String>) -> Self {
2888+
self.stop_reason = Some(stop_reason.into());
28942889
self
28952890
}
28962891

crates/rmcp/src/model/prompt.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,20 @@ impl Prompt {
6868
}
6969

7070
/// Set the human-readable title
71-
pub fn with_title(mut self, title: Option<String>) -> Self {
72-
self.title = title;
71+
pub fn with_title(mut self, title: impl Into<String>) -> Self {
72+
self.title = Some(title.into());
7373
self
7474
}
7575

7676
/// Set the icons
77-
pub fn with_icons(mut self, icons: Option<Vec<Icon>>) -> Self {
78-
self.icons = icons;
77+
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
78+
self.icons = Some(icons);
7979
self
8080
}
8181

8282
/// Set the metadata
83-
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
84-
self.meta = meta;
83+
pub fn with_meta(mut self, meta: Meta) -> Self {
84+
self.meta = Some(meta);
8585
self
8686
}
8787
}

crates/rmcp/src/model/tool.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -261,32 +261,32 @@ impl Tool {
261261
}
262262

263263
/// Set the human-readable title
264-
pub fn with_title(mut self, title: Option<String>) -> Self {
265-
self.title = title;
264+
pub fn with_title(mut self, title: impl Into<String>) -> Self {
265+
self.title = Some(title.into());
266266
self
267267
}
268268

269269
/// Set the output schema from a raw value
270-
pub fn with_raw_output_schema(mut self, output_schema: Option<Arc<JsonObject>>) -> Self {
271-
self.output_schema = output_schema;
270+
pub fn with_raw_output_schema(mut self, output_schema: Arc<JsonObject>) -> Self {
271+
self.output_schema = Some(output_schema);
272272
self
273273
}
274274

275275
/// Set the annotations
276-
pub fn with_annotations(mut self, annotations: Option<ToolAnnotations>) -> Self {
277-
self.annotations = annotations;
276+
pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
277+
self.annotations = Some(annotations);
278278
self
279279
}
280280

281281
/// Set the icons
282-
pub fn with_icons(mut self, icons: Option<Vec<Icon>>) -> Self {
283-
self.icons = icons;
282+
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
283+
self.icons = Some(icons);
284284
self
285285
}
286286

287287
/// Set the metadata
288-
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
289-
self.meta = meta;
288+
pub fn with_meta(mut self, meta: Meta) -> Self {
289+
self.meta = Some(meta);
290290
self
291291
}
292292

@@ -298,8 +298,8 @@ impl Tool {
298298
}
299299

300300
/// Set the execution configuration for this tool.
301-
pub fn with_execution(mut self, execution: Option<ToolExecution>) -> Self {
302-
self.execution = execution;
301+
pub fn with_execution(mut self, execution: ToolExecution) -> Self {
302+
self.execution = Some(execution);
303303
self
304304
}
305305

crates/rmcp/tests/common/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl ClientHandler for TestClientHandler {
7878
SamplingMessage::assistant_text(response.to_string()),
7979
"test-model".to_string(),
8080
)
81-
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string())))
81+
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN))
8282
}
8383

8484
fn on_logging_message(

crates/rmcp/tests/test_sampling.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async fn test_sampling_result_structure() -> Result<()> {
5555
SamplingMessage::assistant_text("The capital of France is Paris."),
5656
"test-model".to_string(),
5757
)
58-
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
58+
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN);
5959

6060
let json = serde_json::to_string(&result)?;
6161
let deserialized: CreateMessageResult = serde_json::from_str(&json)?;
@@ -436,7 +436,7 @@ async fn test_create_message_result_tool_use_stop_reason() -> Result<()> {
436436
),
437437
"test-model".to_string(),
438438
)
439-
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_TOOL_USE.to_string()));
439+
.with_stop_reason(CreateMessageResult::STOP_REASON_TOOL_USE);
440440

441441
let json = serde_json::to_string(&result)?;
442442
let deserialized: CreateMessageResult = serde_json::from_str(&json)?;
@@ -688,7 +688,7 @@ async fn test_create_message_result_validate_rejects_user_role() {
688688
SamplingMessage::user_text("This should not be a user message"),
689689
"test-model".to_string(),
690690
)
691-
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
691+
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN);
692692

693693
let err = result.validate().unwrap_err();
694694
assert!(
@@ -703,7 +703,7 @@ async fn test_create_message_result_validate_accepts_assistant_role() {
703703
SamplingMessage::assistant_text("Hello!"),
704704
"test-model".to_string(),
705705
)
706-
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
706+
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN);
707707

708708
assert!(result.validate().is_ok());
709709
}

crates/rmcp/tests/test_task_support_validation.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use rmcp::{
1313
model::{CallToolRequestParams, ClientInfo, ErrorCode, JsonObject},
1414
tool, tool_handler, tool_router,
1515
};
16-
use serde_json::json;
1716

1817
/// Server with tools having different task support modes.
1918
#[derive(Debug, Clone)]
@@ -75,8 +74,8 @@ impl ClientHandler for DummyClientHandler {
7574
}
7675

7776
/// Helper to create a task object for tool calls
78-
fn make_task() -> Option<JsonObject> {
79-
Some(json!({}).as_object().unwrap().clone())
77+
fn make_task() -> JsonObject {
78+
serde_json::Map::new()
8079
}
8180

8281
#[tokio::test]

examples/clients/src/sampling_stdio.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl ClientHandler for SamplingDemoClient {
4444
SamplingMessage::assistant_text(response_text),
4545
"mock_llm".to_string(),
4646
)
47-
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string())))
47+
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN))
4848
}
4949
}
5050

0 commit comments

Comments
 (0)