Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions crates/rmcp-macros/src/prompt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use darling::{FromMeta, ast::NestedMeta};
use proc_macro2::{Span, TokenStream};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Expr, Ident, ImplItemFn, ReturnType};

Expand All @@ -12,8 +12,10 @@ pub struct PromptAttribute {
pub name: Option<String>,
/// Human readable title of prompt
pub title: Option<String>,
/// Optional description of what the prompt does
pub description: Option<String>,
/// Optional description of what the prompt does. A string literal, an
/// expression that evaluates to a `&'static str` (such as a `const` path
/// or `concat!`), or a `#[doc]` attribute on the function.
pub description: Option<darling::util::PreservedStrExpr>,
/// Arguments that can be passed to the prompt
pub arguments: Option<Expr>,
/// Optional icons for the prompt
Expand Down Expand Up @@ -104,11 +106,8 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
};

let name = attribute.name.unwrap_or_else(|| fn_ident.to_string());
let description = if let Some(s) = attribute.description {
Some(Expr::Lit(syn::ExprLit {
attrs: Vec::new(),
lit: syn::Lit::Str(syn::LitStr::new(&s, Span::call_site())),
}))
let description = if let Some(description) = attribute.description {
Some(description.into())
} else {
fn_item.attrs.iter().try_fold(None, extract_doc_line)?
};
Expand Down
19 changes: 9 additions & 10 deletions crates/rmcp-macros/src/tool.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use darling::{FromMeta, ast::NestedMeta};
use proc_macro2::{Span, TokenStream};
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{Expr, Ident, ImplItemFn, LitStr, ReturnType, parse_quote};
use syn::{Expr, Ident, ImplItemFn, ReturnType, parse_quote};

use crate::common::extract_doc_line;

Expand Down Expand Up @@ -65,7 +65,10 @@ pub struct ToolAttribute {
pub name: Option<String>,
/// Human readable title of tool
pub title: Option<String>,
pub description: Option<String>,
/// The description of the tool. A string literal, an expression that
/// evaluates to a `&'static str` (such as a `const` path or `concat!`),
/// or a `#[doc]` attribute on the function.
pub description: Option<darling::util::PreservedStrExpr>,
/// A JSON Schema object defining the expected parameters for the tool
pub input_schema: Option<Expr>,
/// An optional JSON Schema object defining the structure of the tool's output
Expand Down Expand Up @@ -255,13 +258,9 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
}
});

let description_expr = if let Some(s) = attribute.description {
Some(Expr::Lit(syn::ExprLit {
attrs: Vec::new(),
lit: syn::Lit::Str(LitStr::new(&s, Span::call_site())),
}))
} else {
fn_item.attrs.iter().try_fold(None, extract_doc_line)?
let description_expr = match attribute.description {
Some(description) => Some(description.into()),
None => fn_item.attrs.iter().try_fold(None, extract_doc_line)?,
};
let resolved_tool_attr = ResolvedToolAttribute {
name: attribute.name.unwrap_or_else(|| fn_ident.to_string()),
Expand Down
17 changes: 17 additions & 0 deletions crates/rmcp/tests/test_prompt_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,15 @@ impl Server {
"This is a prompt with no parameters.".to_string(),
)]
}

#[prompt(description = CONST_PROMPT_DESCRIPTION)]
async fn const_description_prompt(&self) -> Vec<PromptMessage> {
vec![]
}
}

const CONST_PROMPT_DESCRIPTION: &str = "Prompt description from a const";

// define generic service trait
pub trait DataService: Send + Sync + 'static {
fn get_context(&self) -> String;
Expand Down Expand Up @@ -155,6 +162,16 @@ async fn test_prompt_macros_with_empty_param() {
);
}

#[test]
fn test_prompt_description_accepts_const_expr() {
// tests https://github.com/modelcontextprotocol/rust-sdk/issues/1175
let prompt = Server::const_description_prompt_prompt_attr();
assert_eq!(
prompt.description.as_deref(),
Some(CONST_PROMPT_DESCRIPTION)
);
}

#[tokio::test]
async fn test_prompt_macros_with_generics() {
let mock_service = MockDataService;
Expand Down
18 changes: 18 additions & 0 deletions crates/rmcp/tests/test_tool_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ impl Server {

#[tool]
async fn empty_param(&self) {}

#[tool(description = CONST_TOOL_DESCRIPTION)]
async fn const_description_tool(&self) {}

#[tool(description = concat!("part-a-", "part-b"))]
async fn concat_description_tool(&self) {}
}

const CONST_TOOL_DESCRIPTION: &str = "Description from a const";

#[test]
fn test_description_accepts_const_and_concat_exprs() {
// tests https://github.com/modelcontextprotocol/rust-sdk/issues/1175
let tool = Server::const_description_tool_tool_attr();
assert_eq!(tool.description.as_deref(), Some(CONST_TOOL_DESCRIPTION));

let tool = Server::concat_description_tool_tool_attr();
assert_eq!(tool.description.as_deref(), Some("part-a-part-b"));
}

/// Generic service trait.
Expand Down