Summary
The #[tool] macro correctly omits + Send on generated handler futures when the local crate feature is enabled or #[tool(local)] is used. The #[prompt] macro does not — it unconditionally emits + Send on all generated futures, making it impossible to use !Send types in prompt handlers even with the local feature.
Context
The local feature was added in #740 to support !Send tool handlers (e.g., single-threaded platform APIs like Apple's EventKit on macOS). The #[tool] macro was updated to check for the feature:
rmcp-macros/src/tool.rs:339-341:
// 2. make return type: `std::pin::Pin<Box<dyn std::future::Future<Output = #ReturnType> + Send + '_>>`
// (omit `+ Send` when the `local` crate feature is active or `#[tool(local)]` is used)
let omit_send = cfg!(feature = "local") || attribute.local;
The #[prompt] macro was not updated and unconditionally emits + Send:
rmcp-macros/src/prompt.rs:141:
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + Send + #lt>> }
rmcp-macros/src/prompt.rs:144:
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + Send + #lt>> }
Reproduction
Enable the local feature and define a server struct holding a !Send type with both tool and prompt handlers:
use rmcp::*;
use std::rc::Rc; // !Send
struct MyServer {
data: Rc<String>, // !Send
}
#[tool_router]
impl MyServer {
#[tool(name = "greet")]
async fn greet(&self) -> Result<CallToolResult, McpError> {
// ✅ Compiles with `local` feature — future is not required to be Send
Ok(CallToolResult::text(format!("Hello: {}", self.data)))
}
}
#[prompt_router]
impl MyServer {
#[prompt(name = "greeting")]
async fn greeting(&self) -> Result<GetPromptResult, McpError> {
// ❌ Fails to compile — future is required to be Send because
// #[prompt] unconditionally emits `+ Send`
Ok(GetPromptResult::new(vec![PromptMessage::new_text(
PromptMessageRole::User,
format!("Greeting: {}", self.data),
)]))
}
}
Speculative Fix
Apply the same omit_send logic from tool.rs to prompt.rs. In rmcp-macros/src/prompt.rs, around line 130-145 where the return type is generated:
// Add this (matching tool.rs:339-341)
let omit_send = cfg!(feature = "local");
// Then change lines 141 and 144 from:
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + Send + #lt>> }
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + Send + #lt>> }
// To:
if omit_send {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + #lt>> }
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + #lt>> }
} else {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + Send + #lt>> }
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + Send + #lt>> }
}
Optionally, also support the per-handler #[prompt(local)] attribute for parity with #[tool(local)].
Use Case
eventkit-rs is an MCP server wrapping Apple's EventKit framework. The underlying Objective-C managers (EKEventStore, EKReminderStore) are !Send. Currently every tool and prompt call must allocate a fresh manager because the local feature can't be used — the #[prompt] handlers reference &self and the macro forces Send on the future. With this fix, managers could be stored directly on the server struct and reused across calls.
Summary
The
#[tool]macro correctly omits+ Sendon generated handler futures when thelocalcrate feature is enabled or#[tool(local)]is used. The#[prompt]macro does not — it unconditionally emits+ Sendon all generated futures, making it impossible to use!Sendtypes in prompt handlers even with thelocalfeature.Context
The
localfeature was added in #740 to support!Sendtool handlers (e.g., single-threaded platform APIs like Apple's EventKit on macOS). The#[tool]macro was updated to check for the feature:rmcp-macros/src/tool.rs:339-341:The
#[prompt]macro was not updated and unconditionally emits+ Send:rmcp-macros/src/prompt.rs:141:rmcp-macros/src/prompt.rs:144:Reproduction
Enable the
localfeature and define a server struct holding a!Sendtype with both tool and prompt handlers:Speculative Fix
Apply the same
omit_sendlogic fromtool.rstoprompt.rs. Inrmcp-macros/src/prompt.rs, around line 130-145 where the return type is generated:Optionally, also support the per-handler
#[prompt(local)]attribute for parity with#[tool(local)].Use Case
eventkit-rs is an MCP server wrapping Apple's EventKit framework. The underlying Objective-C managers (
EKEventStore,EKReminderStore) are!Send. Currently every tool and prompt call must allocate a fresh manager because thelocalfeature can't be used — the#[prompt]handlers reference&selfand the macro forcesSendon the future. With this fix, managers could be stored directly on the server struct and reused across calls.