Skip to content

Commit ddef4ce

Browse files
RobJellinghausRobJellinghaus
andauthored
fix(clippy): add doc comment for generated tool attr fn (#439)
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. Co-authored-by: RobJellinghaus <rjellinghaus@live.com>
1 parent 4ee2add commit ddef4ce

File tree

2 files changed

+26
-7
lines changed

2 files changed

+26
-7
lines changed

crates/rmcp-macros/src/tool.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use darling::{FromMeta, ast::NestedMeta};
22
use proc_macro2::TokenStream;
33
use quote::{ToTokens, format_ident, quote};
4-
use syn::{Expr, Ident, ImplItemFn, ReturnType};
4+
use syn::{Expr, Ident, ImplItemFn, ReturnType, parse_quote};
55

66
use crate::common::{extract_doc_line, none_expr};
77

@@ -120,7 +120,10 @@ impl ResolvedToolAttribute {
120120
} else {
121121
quote! { None }
122122
};
123+
let doc_comment = format!("Generated tool metadata function for {name}");
124+
let doc_attr: syn::Attribute = parse_quote!(#[doc = #doc_comment]);
123125
let tokens = quote! {
126+
#doc_attr
124127
pub fn #fn_ident() -> rmcp::model::Tool {
125128
rmcp::model::Tool {
126129
name: #name.into(),

crates/rmcp/tests/test_tool_macros.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
//! Test tool macros, including documentation for generated fns.
2+
13
//cargo test --test test_tool_macros --features "client server"
4+
// Enforce that all generated code has sufficient docs to pass missing_docs lint
5+
#![deny(missing_docs)]
26
#![allow(dead_code)]
37
use std::sync::Arc;
48

@@ -11,22 +15,27 @@ use rmcp::{
1115
use schemars::JsonSchema;
1216
use serde::{Deserialize, Serialize};
1317

18+
/// Parameters for weather tool.
1419
#[derive(Serialize, Deserialize, JsonSchema)]
1520
pub struct GetWeatherRequest {
21+
/// City of interest.
1622
pub city: String,
23+
/// Date of interest.
1724
pub date: String,
1825
}
1926

2027
#[tool_handler(router = self.tool_router)]
2128
impl ServerHandler for Server {}
2229

30+
/// Trivial stateless server.
2331
#[derive(Debug, Clone)]
2432
#[allow(dead_code)]
2533
pub struct Server {
2634
tool_router: ToolRouter<Self>,
2735
}
2836

2937
impl Server {
38+
/// Create weather server.
3039
pub fn new() -> Self {
3140
Self {
3241
tool_router: Self::tool_router(),
@@ -53,8 +62,9 @@ impl Server {
5362
async fn empty_param(&self) {}
5463
}
5564

56-
// define generic service trait
65+
/// Generic service trait.
5766
pub trait DataService: Send + Sync + 'static {
67+
/// Get data from service.
5868
fn get_data(&self) -> String;
5969
}
6070

@@ -67,7 +77,7 @@ impl DataService for MockDataService {
6777
}
6878
}
6979

70-
// define generic server
80+
/// Generic server.
7181
#[derive(Debug, Clone)]
7282
pub struct GenericServer<DS: DataService> {
7383
data_service: Arc<DS>,
@@ -76,6 +86,7 @@ pub struct GenericServer<DS: DataService> {
7686

7787
#[tool_router]
7888
impl<DS: DataService> GenericServer<DS> {
89+
/// Create data server instance.
7990
pub fn new(data_service: DS) -> Self {
8091
Self {
8192
data_service: Arc::new(data_service),
@@ -142,22 +153,26 @@ async fn test_tool_macros_with_optional_param() {
142153

143154
impl GetWeatherRequest {}
144155

145-
// Struct defined for testing optional field schema generation
156+
/// Struct defined for testing optional field schema generation.
146157
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
147158
pub struct OptionalFieldTestSchema {
159+
/// Field description.
148160
#[schemars(description = "An optional description field")]
149161
pub description: Option<String>,
150162
}
151163

152-
// Struct defined for testing optional i64 field schema generation and null handling
164+
/// Struct defined for testing optional i64 field schema generation and null handling.
153165
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
154166
pub struct OptionalI64TestSchema {
167+
/// Optional count field.
155168
#[schemars(description = "An optional i64 field")]
156169
pub count: Option<i64>,
157-
pub mandatory_field: String, // Added to ensure non-empty object schema
170+
171+
/// Added to ensure non-empty object schema.
172+
pub mandatory_field: String,
158173
}
159174

160-
// Dummy struct to host the test tool method
175+
/// Dummy struct to host the test tool method.
161176
#[derive(Debug, Clone)]
162177
pub struct OptionalSchemaTester {
163178
tool_router: ToolRouter<Self>,
@@ -170,6 +185,7 @@ impl Default for OptionalSchemaTester {
170185
}
171186

172187
impl OptionalSchemaTester {
188+
/// Create instance of optional schema tester service.
173189
pub fn new() -> Self {
174190
Self {
175191
tool_router: Self::tool_router(),

0 commit comments

Comments
 (0)