Skip to content
28 changes: 10 additions & 18 deletions crates/rmcp-macros/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,31 +46,23 @@ impl ResolvedPromptAttribute {
} else {
quote! { None::<String> }
};
let title = if let Some(title) = title {
quote! { Some(#title.into()) }
} else {
quote! { None }
};
let icons = if let Some(icons) = icons {
quote! { Some(#icons) }
} else {
quote! { None }
};
let meta = if let Some(meta) = meta {
quote! { Some(#meta) }
} else {
quote! { None }
};
let title_call = title
.map(|t| quote! { .with_title(#t) })
.unwrap_or_default();
let icons_call = icons
.map(|i| quote! { .with_icons(#i) })
.unwrap_or_default();
let meta_call = meta.map(|m| quote! { .with_meta(#m) }).unwrap_or_default();
let tokens = quote! {
pub fn #fn_ident() -> rmcp::model::Prompt {
rmcp::model::Prompt::from_raw(
#name,
#description,
#arguments,
)
.with_title(#title)
.with_icons(#icons)
.with_meta(#meta)
#title_call
#icons_call
#meta_call
}
};
syn::parse2::<ImplItemFn>(tokens)
Expand Down
49 changes: 22 additions & 27 deletions crates/rmcp-macros/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,42 +134,37 @@ impl ResolvedToolAttribute {
} else {
quote! { None }
};
let output_schema = if let Some(output_schema) = output_schema {
quote! { Some(#output_schema) }
} else {
quote! { None }
};
let title = if let Some(title) = title {
quote! { Some(#title.into()) }
} else {
quote! { None }
};
let icons = if let Some(icons) = icons {
quote! { Some(#icons) }
} else {
quote! { None }
};
let meta = if let Some(meta) = meta {
quote! { Some(#meta) }
} else {
quote! { None }
};
let title_call = title
.map(|t| quote! { .with_title(#t) })
.unwrap_or_default();
let output_schema_call = output_schema
.map(|s| quote! { .with_raw_output_schema(#s) })
.unwrap_or_default();
let icons_call = icons
.map(|i| quote! { .with_icons(#i) })
.unwrap_or_default();
let meta_call = meta.map(|m| quote! { .with_meta(#m) }).unwrap_or_default();
let doc_comment = format!("Generated tool metadata function for {name}");
let doc_attr: syn::Attribute = parse_quote!(#[doc = #doc_comment]);
let tokens = quote! {
#doc_attr
pub fn #fn_ident() -> rmcp::model::Tool {
rmcp::model::Tool::new_with_raw(
let mut __tool = rmcp::model::Tool::new_with_raw(
#name,
#description,
#input_schema,
)
.with_title(#title)
.with_raw_output_schema(#output_schema)
.with_annotations(#annotations)
.with_execution(#execution)
.with_icons(#icons)
.with_meta(#meta)
#title_call
#output_schema_call
#icons_call
#meta_call;
if let Some(__annotations) = #annotations {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why are annotations and execution done this way instead of the same way as title/icons/meta?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch, @alexhancock! Refactored the code so all optional fields follow the same pattern and the generated code is a clean method chain again.

__tool = __tool.with_annotations(__annotations);
}
if let Some(__execution) = #execution {
__tool = __tool.with_execution(__execution);
}
__tool
}
};
syn::parse2::<ImplItemFn>(tokens)
Expand Down
29 changes: 12 additions & 17 deletions crates/rmcp/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1455,13 +1455,10 @@ impl LoggingMessageNotificationParam {
}
}

/// Create with a logger name.
pub fn with_logger(level: LoggingLevel, logger: impl Into<String>, data: Value) -> Self {
Self {
level,
logger: Some(logger.into()),
data,
}
/// Set the logger name.
pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
self.logger = Some(logger.into());
self
}
}

Expand Down Expand Up @@ -2605,12 +2602,10 @@ impl CreateElicitationResult {
}
}

/// Create with content.
pub fn with_content(action: ElicitationAction, content: Value) -> Self {
Self {
action,
content: Some(content),
}
/// Set the content on this result.
pub fn with_content(mut self, content: Value) -> Self {
self.content = Some(content);
self
}
}

Expand Down Expand Up @@ -2822,8 +2817,8 @@ impl CallToolRequestParams {
}

/// Sets the task metadata for this tool call.
pub fn with_task(mut self, task: Option<JsonObject>) -> Self {
self.task = task;
pub fn with_task(mut self, task: JsonObject) -> Self {
self.task = Some(task);
self
}
}
Expand Down Expand Up @@ -2889,8 +2884,8 @@ impl CreateMessageResult {
pub const STOP_REASON_TOOL_USE: &str = "toolUse";

/// Set the stop reason.
pub fn with_stop_reason(mut self, stop_reason: Option<String>) -> Self {
self.stop_reason = stop_reason;
pub fn with_stop_reason(mut self, stop_reason: impl Into<String>) -> Self {
self.stop_reason = Some(stop_reason.into());
self
}

Expand Down
12 changes: 6 additions & 6 deletions crates/rmcp/src/model/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,20 @@ impl Prompt {
}

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

/// Set the icons
pub fn with_icons(mut self, icons: Option<Vec<Icon>>) -> Self {
self.icons = icons;
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
self.icons = Some(icons);
self
}

/// Set the metadata
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
self.meta = meta;
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = Some(meta);
self
}
}
Expand Down
24 changes: 12 additions & 12 deletions crates/rmcp/src/model/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,32 +261,32 @@ impl Tool {
}

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

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

/// Set the annotations
pub fn with_annotations(mut self, annotations: Option<ToolAnnotations>) -> Self {
self.annotations = annotations;
pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
self.annotations = Some(annotations);
self
}

/// Set the icons
pub fn with_icons(mut self, icons: Option<Vec<Icon>>) -> Self {
self.icons = icons;
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
self.icons = Some(icons);
self
}

/// Set the metadata
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
self.meta = meta;
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = Some(meta);
self
}

Expand All @@ -298,8 +298,8 @@ impl Tool {
}

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

Expand Down
2 changes: 1 addition & 1 deletion crates/rmcp/tests/common/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl ClientHandler for TestClientHandler {
SamplingMessage::assistant_text(response.to_string()),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string())))
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN))
}

fn on_logging_message(
Expand Down
8 changes: 4 additions & 4 deletions crates/rmcp/tests/test_sampling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async fn test_sampling_result_structure() -> Result<()> {
SamplingMessage::assistant_text("The capital of France is Paris."),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN);

let json = serde_json::to_string(&result)?;
let deserialized: CreateMessageResult = serde_json::from_str(&json)?;
Expand Down Expand Up @@ -436,7 +436,7 @@ async fn test_create_message_result_tool_use_stop_reason() -> Result<()> {
),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_TOOL_USE.to_string()));
.with_stop_reason(CreateMessageResult::STOP_REASON_TOOL_USE);

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

let err = result.validate().unwrap_err();
assert!(
Expand All @@ -703,7 +703,7 @@ async fn test_create_message_result_validate_accepts_assistant_role() {
SamplingMessage::assistant_text("Hello!"),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN);

assert!(result.validate().is_ok());
}
5 changes: 2 additions & 3 deletions crates/rmcp/tests/test_task_support_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use rmcp::{
model::{CallToolRequestParams, ClientInfo, ErrorCode, JsonObject},
tool, tool_handler, tool_router,
};
use serde_json::json;

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

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

#[tokio::test]
Expand Down
2 changes: 1 addition & 1 deletion examples/clients/src/sampling_stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl ClientHandler for SamplingDemoClient {
SamplingMessage::assistant_text(response_text),
"mock_llm".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string())))
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN))
}
}

Expand Down