Skip to content

Commit 3a97917

Browse files
authored
feat(rmcp-macro): generate description from docs (#141)
* feat: generate description from docs * fix logic to extract doc * fmt * chore: undo unnecessary changes in `Cargo.toml` * chore: undo unnecessary changes in `Cargo.toml` * chore: remove unnecessary code in tests * create `extract_doc_line` * fmt * make sure the string is not empty * avoid multilayer nesting * fmt
1 parent 9d6f9a2 commit 3a97917

File tree

1 file changed

+76
-1
lines changed

1 file changed

+76
-1
lines changed

crates/rmcp-macros/src/tool.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,35 @@ pub(crate) fn tool_impl_item(attr: TokenStream, mut input: ItemImpl) -> syn::Res
296296
})
297297
}
298298

299+
fn extract_doc_line(attr: &syn::Attribute) -> Option<String> {
300+
if !attr.path().is_ident("doc") {
301+
return None;
302+
}
303+
304+
let name_value = match &attr.meta {
305+
syn::Meta::NameValue(nv) => nv,
306+
_ => return None,
307+
};
308+
309+
let expr_lit = match &name_value.value {
310+
syn::Expr::Lit(lit) => lit,
311+
_ => return None,
312+
};
313+
314+
let lit_str = match &expr_lit.lit {
315+
syn::Lit::Str(s) => s,
316+
_ => return None,
317+
};
318+
319+
let content = lit_str.value().trim().to_string();
320+
321+
if content.is_empty() {
322+
None
323+
} else {
324+
Some(content)
325+
}
326+
}
327+
299328
pub(crate) fn tool_fn_item(attr: TokenStream, mut input_fn: ItemFn) -> syn::Result<TokenStream> {
300329
let mut tool_macro_attrs = ToolAttrs::default();
301330
let args: ToolFnItemAttrs = syn::parse2(attr)?;
@@ -405,10 +434,19 @@ pub(crate) fn tool_fn_item(attr: TokenStream, mut input_fn: ItemFn) -> syn::Resu
405434
// generate get tool attr function
406435
let tool_attr_fn = {
407436
let description = if let Some(expr) = tool_macro_attrs.fn_item.description {
437+
// Use explicitly provided description if available
408438
expr
409439
} else {
440+
// Try to extract documentation comments
441+
let doc_content = input_fn
442+
.attrs
443+
.iter()
444+
.filter_map(extract_doc_line)
445+
.collect::<Vec<_>>()
446+
.join("\n");
447+
410448
parse_quote! {
411-
""
449+
#doc_content.trim().to_string()
412450
}
413451
};
414452
let schema = match &tool_macro_attrs.params {
@@ -657,4 +695,41 @@ mod test {
657695
println!("input: {:#}", input);
658696
Ok(())
659697
}
698+
#[test]
699+
fn test_doc_comment_description() -> syn::Result<()> {
700+
let attr = quote! {}; // No explicit description
701+
let input = quote! {
702+
/// This is a test description from doc comments
703+
/// with multiple lines
704+
fn test_function(&self) -> Result<(), Error> {
705+
Ok(())
706+
}
707+
};
708+
let result = tool(attr, input)?;
709+
710+
// The output should contain the description from doc comments
711+
let result_str = result.to_string();
712+
assert!(result_str.contains("This is a test description from doc comments"));
713+
assert!(result_str.contains("with multiple lines"));
714+
715+
Ok(())
716+
}
717+
#[test]
718+
fn test_explicit_description_priority() -> syn::Result<()> {
719+
let attr = quote! {
720+
description = "Explicit description has priority"
721+
};
722+
let input = quote! {
723+
/// Doc comment description that should be ignored
724+
fn test_function(&self) -> Result<(), Error> {
725+
Ok(())
726+
}
727+
};
728+
let result = tool(attr, input)?;
729+
730+
// The output should contain the explicit description
731+
let result_str = result.to_string();
732+
assert!(result_str.contains("Explicit description has priority"));
733+
Ok(())
734+
}
660735
}

0 commit comments

Comments
 (0)