Skip to content
Merged
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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,30 @@ jobs:
- name: Spell Check Repo
uses: crate-ci/typos@master

msrv:
name: Check MSRV
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7

- name: Read MSRV from Cargo.toml
run: |
MSRV=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name == "rmcp") | .rust_version')
echo "MSRV=$MSRV" >> "$GITHUB_ENV"

- name: Install MSRV Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.MSRV }}

- uses: Swatinem/rust-cache@v2

- name: Check default workspace members with MSRV
run: cargo +${{ env.MSRV }} check --all-targets --all-features

test:
name: Run Tests
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ rmcp-macros = { version = "3.0.0-beta.1", path = "./crates/rmcp-macros" }

[workspace.package]
edition = "2024"
rust-version = "1.88"
version = "3.0.0-beta.1"
authors = ["4t145 <u4t145@163.com>"]
license = "Apache-2.0"
Expand Down
1 change: 0 additions & 1 deletion clippy.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
msrv = "1.85"
too-many-arguments-threshold = 10
check-private-items = false
1 change: 1 addition & 0 deletions crates/rmcp-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ name = "rmcp-macros"
license = { workspace = true }
version = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
repository = { workspace = true }
homepage = { workspace = true }
readme = { workspace = true }
Expand Down
20 changes: 9 additions & 11 deletions crates/rmcp-macros/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,15 @@ pub fn extract_doc_line(
/// Returns the full Parameters<T> type if found
pub fn find_parameters_type_in_sig(sig: &Signature) -> Option<Box<Type>> {
sig.inputs.iter().find_map(|input| {
if let FnArg::Typed(pat_type) = input {
if let Type::Path(type_path) = &*pat_type.ty {
if type_path
.path
.segments
.last()
.is_some_and(|type_name| type_name.ident == "Parameters")
{
return Some(pat_type.ty.clone());
}
}
if let FnArg::Typed(pat_type) = input
&& let Type::Path(type_path) = &*pat_type.ty
&& type_path
.path
.segments
.last()
.is_some_and(|type_name| type_name.ident == "Parameters")
{
return Some(pat_type.ty.clone());
}
None
})
Expand Down
14 changes: 7 additions & 7 deletions crates/rmcp-macros/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
// 3. make body: { Box::pin(async move { #body }) }
let new_output = syn::parse2::<ReturnType>({
let mut lt = quote! { 'static };
if let Some(receiver) = fn_item.sig.receiver() {
if let Some((_, receiver_lt)) = receiver.reference.as_ref() {
if let Some(receiver_lt) = receiver_lt {
lt = quote! { #receiver_lt };
} else {
lt = quote! { '_ };
}
if let Some(receiver) = fn_item.sig.receiver()
&& let Some((_, receiver_lt)) = receiver.reference.as_ref()
{
if let Some(receiver_lt) = receiver_lt {
lt = quote! { #receiver_lt };
} else {
lt = quote! { '_ };
}
}
match &fn_item.sig.output {
Expand Down
31 changes: 14 additions & 17 deletions crates/rmcp-macros/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ use crate::common::extract_doc_line;

/// Check if a type is Json<T> and extract the inner type T
fn extract_json_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
if let syn::Type::Path(type_path) = ty {
if let Some(last_segment) = type_path.path.segments.last() {
if last_segment.ident == "Json" {
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments {
if let Some(syn::GenericArgument::Type(inner_type)) = args.args.first() {
return Some(inner_type);
}
}
}
}
if let syn::Type::Path(type_path) = ty
&& let Some(last_segment) = type_path.path.segments.last()
&& last_segment.ident == "Json"
&& let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
&& let Some(syn::GenericArgument::Type(inner_type)) = args.args.first()
{
return Some(inner_type);
}
None
}
Expand Down Expand Up @@ -286,13 +283,13 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
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() {
if let Some((_, receiver_lt)) = receiver.reference.as_ref() {
if let Some(receiver_lt) = receiver_lt {
lt = quote! { #receiver_lt };
} else {
lt = quote! { '_ };
}
if let Some(receiver) = fn_item.sig.receiver()
&& let Some((_, receiver_lt)) = receiver.reference.as_ref()
{
if let Some(receiver_lt) = receiver_lt {
lt = quote! { #receiver_lt };
} else {
lt = quote! { '_ };
}
}
match &fn_item.sig.output {
Expand Down
1 change: 1 addition & 0 deletions crates/rmcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name = "rmcp"
license = { workspace = true }
version = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
repository = { workspace = true }
homepage = { workspace = true }
readme = { workspace = true }
Expand Down
8 changes: 4 additions & 4 deletions crates/rmcp/src/handler/server/router/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,10 @@ where
}

fn notify_if_visible(&self, name: &str) {
if self.map.contains_key(name) {
if let Some(notifier) = &self.notifier {
notifier();
}
if self.map.contains_key(name)
&& let Some(notifier) = &self.notifier
{
notifier();
}
}

Expand Down
18 changes: 8 additions & 10 deletions crates/rmcp/src/model/elicitation_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -983,17 +983,15 @@ impl EnumSchemaBuilder<MultiSelect> {
return Err("One of the provided default values is not in enum values".to_string());
}
}
if let Some(min) = self.min_items {
if (default_values.len() as u64) < min {
return Err("Number of provided default values is less than min_items".to_string());
}
if let Some(min) = self.min_items
&& (default_values.len() as u64) < min
{
return Err("Number of provided default values is less than min_items".to_string());
}
if let Some(max) = self.max_items {
if (default_values.len() as u64) > max {
return Err(
"Number of provided default values is greater than max_items".to_string(),
);
}
if let Some(max) = self.max_items
&& (default_values.len() as u64) > max
{
return Err("Number of provided default values is greater than max_items".to_string());
}
self.default = default_values;
Ok(self)
Expand Down
18 changes: 7 additions & 11 deletions crates/rmcp/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,10 @@ impl<R: ServiceRole> RequestHandle<R> {
None => None,
}
}, if reset_timeout_on_progress && idle_sleep.is_some() && self.progress_reset_rx.is_some() => {
if progress.is_some() {
if let Some((timeout, sleep)) = idle_sleep.as_mut() {
if progress.is_some()
&& let Some((timeout, sleep)) = idle_sleep.as_mut() {
sleep.as_mut().reset(tokio::time::Instant::now() + *timeout);
}
}
}
}
}
Expand Down Expand Up @@ -1351,11 +1350,10 @@ where
tracing::trace!(?evt, "new event");
match evt {
Event::SendTaskResult(SendTaskResult::Request { id, result }) => {
if let Err(e) = result {
if let Some(responder) = local_responder_pool.remove(&id) {
if let Err(e) = result
&& let Some(responder) = local_responder_pool.remove(&id) {
let _ = responder.send(Err(ServiceError::TransportSend(e)));
}
}
}
Event::SendTaskResult(SendTaskResult::Notification {
responder,
Expand All @@ -1368,16 +1366,14 @@ where
Ok(())
};
let _ = responder.send(response);
if let Some(param) = cancellation_param {
if let Some(request_id) = &param.request_id {
if let Some(responder) = local_responder_pool.remove(request_id) {
if let Some(param) = cancellation_param
&& let Some(request_id) = &param.request_id
&& let Some(responder) = local_responder_pool.remove(request_id) {
tracing::info!(id = %request_id, reason = param.reason, "cancelled");
let _response_result = responder.send(Err(ServiceError::Cancelled {
reason: param.reason.clone(),
}));
}
}
}
}
Event::ResponseSendTaskResult(result) => {
if let Err(error) = result {
Expand Down
12 changes: 5 additions & 7 deletions crates/rmcp/src/transport/async_rw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,12 @@ fn try_parse_with_compatibility<T: serde::de::DeserializeOwned>(
Ok(item) => Ok(Some(item)),
Err(e) => {
// Check if this is a notification that should be ignored for compatibility
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(line_str) {
if let Some(method) =
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(line_str)
&& let Some(method) =
json_value.get("method").and_then(serde_json::Value::as_str)
{
if should_ignore_notification(&json_value, method) {
return Ok(None);
}
}
&& should_ignore_notification(&json_value, method)
{
return Ok(None);
}

tracing::debug!(
Expand Down
Loading