From 116d8a4cfbdec7f821d1b2110fec201d66d7dd69 Mon Sep 17 00:00:00 2001 From: RobJellinghaus Date: Sun, 14 Sep 2025 10:25:44 -0700 Subject: [PATCH] fix(clippy): add doc comment for generated tool attr fn This change makes the `tool` macro's output safe for the `missing_docs` lint, by emitting a doc comment for the generated `[tool]_tool_attr` function. This doc comment is emitted regardless of whether the tool function is public, for simplicity. We are building an MCP server using `rmcp` and discovered that the current crate was not compatible with the `#![deny(missing_docs)]` lint which we use everywhere. Tested by modifying the `test_tool_macros.rs` test to use `#![deny(missing_docs)]` and adding doc comments to all pub fns and structs in that file. None. Fixes #438. --- crates/rmcp-macros/src/tool.rs | 5 ++++- crates/rmcp/tests/test_tool_macros.rs | 28 +++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/rmcp-macros/src/tool.rs b/crates/rmcp-macros/src/tool.rs index 68d5a70a1..07863215e 100644 --- a/crates/rmcp-macros/src/tool.rs +++ b/crates/rmcp-macros/src/tool.rs @@ -1,7 +1,7 @@ use darling::{FromMeta, ast::NestedMeta}; use proc_macro2::TokenStream; use quote::{ToTokens, format_ident, quote}; -use syn::{Expr, Ident, ImplItemFn, ReturnType}; +use syn::{Expr, Ident, ImplItemFn, ReturnType, parse_quote}; use crate::common::{extract_doc_line, none_expr}; @@ -120,7 +120,10 @@ impl ResolvedToolAttribute { } else { quote! { None } }; + let doc_comment = format!("Generated tool metadata function for {name}"); + let doc_attr: syn::Attribute = parse_quote!(#[doc = #doc_comment]); let tokens = quote! { + #doc_attr pub fn #fn_ident() -> rmcp::model::Tool { rmcp::model::Tool { name: #name.into(), diff --git a/crates/rmcp/tests/test_tool_macros.rs b/crates/rmcp/tests/test_tool_macros.rs index 8825f1229..1499f9ec5 100644 --- a/crates/rmcp/tests/test_tool_macros.rs +++ b/crates/rmcp/tests/test_tool_macros.rs @@ -1,4 +1,8 @@ +//! Test tool macros, including documentation for generated fns. + //cargo test --test test_tool_macros --features "client server" +// Enforce that all generated code has sufficient docs to pass missing_docs lint +#![deny(missing_docs)] #![allow(dead_code)] use std::sync::Arc; @@ -11,15 +15,19 @@ use rmcp::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +/// Parameters for weather tool. #[derive(Serialize, Deserialize, JsonSchema)] pub struct GetWeatherRequest { + /// City of interest. pub city: String, + /// Date of interest. pub date: String, } #[tool_handler(router = self.tool_router)] impl ServerHandler for Server {} +/// Trivial stateless server. #[derive(Debug, Clone)] #[allow(dead_code)] pub struct Server { @@ -27,6 +35,7 @@ pub struct Server { } impl Server { + /// Create weather server. pub fn new() -> Self { Self { tool_router: Self::tool_router(), @@ -53,8 +62,9 @@ impl Server { async fn empty_param(&self) {} } -// define generic service trait +/// Generic service trait. pub trait DataService: Send + Sync + 'static { + /// Get data from service. fn get_data(&self) -> String; } @@ -67,7 +77,7 @@ impl DataService for MockDataService { } } -// define generic server +/// Generic server. #[derive(Debug, Clone)] pub struct GenericServer { data_service: Arc, @@ -76,6 +86,7 @@ pub struct GenericServer { #[tool_router] impl GenericServer { + /// Create data server instance. pub fn new(data_service: DS) -> Self { Self { data_service: Arc::new(data_service), @@ -142,22 +153,26 @@ async fn test_tool_macros_with_optional_param() { impl GetWeatherRequest {} -// Struct defined for testing optional field schema generation +/// Struct defined for testing optional field schema generation. #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct OptionalFieldTestSchema { + /// Field description. #[schemars(description = "An optional description field")] pub description: Option, } -// Struct defined for testing optional i64 field schema generation and null handling +/// Struct defined for testing optional i64 field schema generation and null handling. #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct OptionalI64TestSchema { + /// Optional count field. #[schemars(description = "An optional i64 field")] pub count: Option, - pub mandatory_field: String, // Added to ensure non-empty object schema + + /// Added to ensure non-empty object schema. + pub mandatory_field: String, } -// Dummy struct to host the test tool method +/// Dummy struct to host the test tool method. #[derive(Debug, Clone)] pub struct OptionalSchemaTester { tool_router: ToolRouter, @@ -170,6 +185,7 @@ impl Default for OptionalSchemaTester { } impl OptionalSchemaTester { + /// Create instance of optional schema tester service. pub fn new() -> Self { Self { tool_router: Self::tool_router(),