Skip to content
Draft
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
2 changes: 2 additions & 0 deletions crates/rmcp-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ serde_json = "1.0"
darling = { version = "0.23" }

[features]
local = []

[dev-dependencies]
17 changes: 15 additions & 2 deletions crates/rmcp-macros/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ pub struct ToolAttribute {
pub icons: Option<Expr>,
/// Optional metadata for the tool
pub meta: Option<Expr>,
/// When true, the generated future will not require `Send`. Useful for `!Send` handlers
/// (e.g. single-threaded database connections). Also enabled globally by the `local` crate feature.
pub local: bool,
}

#[derive(FromMeta, Debug, Default)]
Expand Down Expand Up @@ -333,7 +336,9 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
if fn_item.sig.asyncness.is_some() {
// 1. remove asyncness from sig
// 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)
// 3. make body: { Box::pin(async move { #body }) }
let omit_send = cfg!(feature = "local") || attribute.local;
let new_output = syn::parse2::<ReturnType>({
let mut lt = quote! { 'static };
if let Some(receiver) = fn_item.sig.receiver() {
Expand All @@ -347,10 +352,18 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
}
match &fn_item.sig.output {
syn::ReturnType::Default => {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + Send + #lt>> }
if omit_send {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + #lt>> }
} else {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + Send + #lt>> }
}
}
syn::ReturnType::Type(_, ty) => {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + Send + #lt>> }
if omit_send {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + #lt>> }
} else {
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + Send + #lt>> }
}
}
}
})?;
Expand Down
1 change: 1 addition & 0 deletions crates/rmcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ chrono = { version = "0.4.38", default-features = false, features = [

[features]
default = ["base64", "macros", "server"]
local = ["rmcp-macros?/local"]
client = ["dep:tokio-stream"]
server = ["transport-async-rw", "dep:schemars", "dep:pastey"]
macros = ["dep:rmcp-macros", "dep:pastey"]
Expand Down
2 changes: 1 addition & 1 deletion crates/rmcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The transport layer is pluggable. Two built-in pairs cover the most common cases
| | Client | Server |
|:-:|:-:|:-:|
| **stdio** | [`TokioChildProcess`](crate::transport::TokioChildProcess) | [`stdio`](crate::transport::stdio) |
| **Streamable HTTP** | [`StreamableHttpClientTransport`](crate::transport::StreamableHttpClientTransport) | [`StreamableHttpService`](crate::transport::StreamableHttpService) |
| **Streamable HTTP** | [`StreamableHttpClientTransport`](crate::transport::StreamableHttpClientTransport) | `StreamableHttpService` |

Any type that implements the [`Transport`](crate::transport::Transport) trait can be used. The [`IntoTransport`](crate::transport::IntoTransport) helper trait provides automatic conversions from:

Expand Down
Loading
Loading