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
This commit is contained in:
Dale Seo 2026-03-03 12:05:32 -05:00 • committed by GitHub
commit 2d90b76501
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 99 deletions

View file

@ -46,21 +46,13 @@ 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(
@ -68,9 +60,9 @@ impl ResolvedPromptAttribute {
#description,
#arguments,
)
.with_title(#title)
.with_icons(#icons)
.with_meta(#meta)
#title_call
#icons_call
#meta_call
}
};
syn::parse2::<ImplItemFn>(tokens)

View file

@ -3,7 +3,7 @@ use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, format_ident, quote};
use syn::{Expr, Ident, ImplItemFn, LitStr, ReturnType, parse_quote};
use crate::common::{extract_doc_line, none_expr};
use crate::common::extract_doc_line;
/// Check if a type is Json<T> and extract the inner type T
fn extract_json_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
@ -110,8 +110,8 @@ pub struct ResolvedToolAttribute {
pub description: Option<Expr>,
pub input_schema: Expr,
pub output_schema: Option<Expr>,
pub annotations: Expr,
pub execution: Expr,
pub annotations: Option<Expr>,
pub execution: Option<Expr>,
pub icons: Option<Expr>,
pub meta: Option<Expr>,
}
@ -134,26 +134,22 @@ 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 annotations_call = annotations
.map(|a| quote! { .with_annotations(#a) })
.unwrap_or_default();
let execution_call = execution
.map(|e| quote! { .with_execution(#e) })
.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! {
@ -164,12 +160,12 @@ impl ResolvedToolAttribute {
#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
#annotations_call
#execution_call
#icons_call
#meta_call
}
};
syn::parse2::<ImplItemFn>(tokens)
@ -260,17 +256,17 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
let idempotent_hint = wrap_option(idempotent_hint);
let open_world_hint = wrap_option(open_world_hint);
let token_stream = quote! {
Some(rmcp::model::ToolAnnotations::from_raw(
rmcp::model::ToolAnnotations::from_raw(
#title,
#read_only_hint,
#destructive_hint,
#idempotent_hint,
#open_world_hint,
))
)
};
syn::parse2::<Expr>(token_stream)?
Some(syn::parse2::<Expr>(token_stream)?)
} else {
none_expr()?
None
};
let execution_expr = if let Some(execution) = attribute.execution {
let ToolExecutionAttribute { task_support } = execution;
@ -296,13 +292,13 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
};
let token_stream = quote! {
Some(rmcp::model::ToolExecution::from_raw(
rmcp::model::ToolExecution::from_raw(
#task_support_expr,
))
)
};
syn::parse2::<Expr>(token_stream)?
Some(syn::parse2::<Expr>(token_stream)?)
} else {
none_expr()?
None
};
// Handle output_schema - either explicit or generated from return type
let output_schema_expr = attribute.output_schema.or_else(|| {