You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Feature request: Support for !Send futures in #[tool] handlers (e.g. #[tool(local)]) #728
The #[tool] macro currently requires all tool handler futures to be Send. This Send bound propagates virally through the entire async call chain — not just the handler itself, but every function it awaits transitively.
This creates a fundamental incompatibility with popular async libraries that return !Send futures:
sqlx::raw_sql() — returns a future holding PhantomData<fn(&mut PgConnection)> which is !Send. Cannot be used in any function that's transitively called from a #[tool] handler.
sqlx transactions — transaction futures borrow &mut PgConnection across await points, making them !Send in many patterns.
Other database libraries with similar patterns.
Concrete Example
// This works (sqlx::query returns Send future):#[tool]asyncfnmy_tool(&self) -> Result<String>{
sqlx::query("SELECT 1").execute(&self.pool).await?;Ok("done".into())}// This fails to compile (sqlx::raw_sql returns !Send future):#[tool]asyncfnmy_tool(&self) -> Result<String>{
sqlx::raw_sql("CREATE TABLE IF NOT EXISTS ...").execute(&self.pool).await?;Ok("done".into())}
The error propagates even through indirect calls:
error[E0277]: `(dyn Any + Send + 'static)` cannot be sent between threads safely
Current Workaround
We split SQL strings by ; and execute each statement via sqlx::query() individually. This requires writing a custom SQL statement parser to handle semicolons inside string literals, comments, and dollar-quoted strings — significant complexity for what should be a simple migration runner.
Proposed Solution
Add a #[tool(local)] attribute (or similar) that allows handlers to return !Send futures. Implementation options:
#[tool(local)] — use tokio::task::LocalSet for this specific handler
#[tool(spawn_blocking)] — run the handler in spawn_blocking context
Relax the Send bound globally — if the MCP server doesn't actually need to send handler futures across threads (e.g., if it uses a single-threaded runtime or LocalSet internally)
Activity
DaleSeo commentedon Mar 11, 2026
Hi @Stranmor, I've put up PR #740 to address your request. Could you confirm if this works for you?