Skip to content

Commit d448cfa

Browse files
fix(prompt.rs) added ommit send + test
1 parent 5551aa7 commit d448cfa

1 file changed

Lines changed: 40 additions & 1 deletion

File tree

crates/rmcp-macros/src/prompt.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub struct PromptAttribute {
2020
pub icons: Option<Expr>,
2121
/// Optional metadata for the prompt
2222
pub meta: Option<Expr>,
23+
/// When true, the generated future will not require `Send`. Useful for `!Send` handlers
24+
/// (e.g. single-threaded database connections). Also enabled globally by the `local` crate feature.
25+
pub local: bool,
2326
}
2427

2528
pub struct ResolvedPromptAttribute {
@@ -78,6 +81,7 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
7881
};
7982
let mut fn_item = syn::parse2::<ImplItemFn>(input.clone())?;
8083
let fn_ident = &fn_item.sig.ident;
84+
let omit_send = cfg!(feature = "local") || attribute.local;
8185

8286
let prompt_attr_fn_ident = format_ident!("{}_prompt_attr", fn_ident);
8387

@@ -123,7 +127,8 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
123127
// Modify the input function for async support (same as tool macro)
124128
if fn_item.sig.asyncness.is_some() {
125129
// 1. remove asyncness from sig
126-
// 2. make return type: `futures::future::BoxFuture<'_, #ReturnType>`
130+
// 2. make return type: `std::pin::Pin<Box<dyn std::future::Future<Output = #ReturnType> + Send + '_>>`
131+
// (omit `+ Send` when the `local` crate feature is active or `#[prompt(local)]` is used)
127132
// 3. make body: { Box::pin(async move { #body }) }
128133
let new_output = syn::parse2::<ReturnType>({
129134
let mut lt = quote! { 'static };
@@ -234,4 +239,38 @@ mod test {
234239

235240
Ok(())
236241
}
242+
243+
#[test]
244+
fn test_async_prompt_default_send_behavior() -> syn::Result<()> {
245+
let attr = quote! {};
246+
let input = quote! {
247+
async fn test_prompt_default_send(&self) -> String {
248+
"ok".to_string()
249+
}
250+
};
251+
let result = prompt(attr, input)?;
252+
253+
let result_str = result.to_string();
254+
if cfg!(feature = "local") {
255+
assert!(!result_str.contains("+ Send +"));
256+
} else {
257+
assert!(result_str.contains("+ Send +"));
258+
}
259+
Ok(())
260+
}
261+
262+
#[test]
263+
fn test_async_prompt_local_omits_send() -> syn::Result<()> {
264+
let attr = quote! { local };
265+
let input = quote! {
266+
async fn test_prompt_local_no_send(&self) -> String {
267+
"ok".to_string()
268+
}
269+
};
270+
let result = prompt(attr, input)?;
271+
272+
let result_str = result.to_string();
273+
assert!(!result_str.contains("+ Send +"));
274+
Ok(())
275+
}
237276
}

0 commit comments

Comments
 (0)