Skip to main content

docs_mcp/
server.rs

1use std::sync::Arc;
2
3use rmcp::{
4    ErrorData as McpError,
5    ServerHandler,
6    handler::server::{
7        router::tool::ToolRouter,
8        wrapper::Parameters,
9    },
10    model::*,
11    tool, tool_handler, tool_router,
12};
13
14use crate::tools::{
15    AppState,
16    crate_list::{self, CrateListParams},
17    crate_get::{self, CrateGetParams},
18    crate_readme_get::{self, CrateReadmeGetParams},
19    crate_docs_get::{self, CrateDocsGetParams},
20    crate_item_list::{self, CrateItemListParams},
21    crate_item_get::{self, CrateItemGetParams},
22    crate_impls_list::{self, CrateImplsListParams},
23    crate_versions_list::{self, CrateVersionsListParams},
24    crate_version_get::{self, CrateVersionGetParams},
25    crate_dependencies_list::{self, CrateDependenciesListParams},
26    crate_dependents_list::{self, CrateDependentsListParams},
27    crate_downloads_get::{self, CrateDownloadsGetParams},
28};
29
30#[derive(Clone)]
31pub struct DocsMcpServer {
32    tool_router: ToolRouter<DocsMcpServer>,
33    state: Arc<AppState>,
34}
35
36#[tool_router]
37impl DocsMcpServer {
38    pub fn new_with_state(state: Arc<AppState>) -> Self {
39        Self {
40            tool_router: Self::tool_router(),
41            state,
42        }
43    }
44
45    #[tool(description = "Search crates.io by keyword, category, or free-text query. Returns crate summaries ranked by relevance, download count, or recency. Entry point for crate discovery when you don't have a crate name yet.")]
46    async fn crate_list(
47        &self,
48        Parameters(params): Parameters<CrateListParams>,
49    ) -> Result<CallToolResult, McpError> {
50        crate_list::execute(&self.state, params).await
51    }
52
53    #[tool(description = "Get comprehensive metadata for a single crate: description, homepage, repository, download counts, latest stable version, feature flag definitions, and MSRV. Combines crates.io API with the sparse index for authoritative feature map.")]
54    async fn crate_get(
55        &self,
56        Parameters(params): Parameters<CrateGetParams>,
57    ) -> Result<CallToolResult, McpError> {
58        crate_get::execute(&self.state, params).await
59    }
60
61    #[tool(description = "Fetch the crate's README for a specific version as readable text. Contains the author's intended narrative: why the crate exists, how it compares to alternatives, installation instructions, and quick-start examples. Prefer crate_docs_get when you want structured docs plus a module tree; use this tool when you want the raw README prose.")]
62    async fn crate_readme_get(
63        &self,
64        Parameters(params): Parameters<CrateReadmeGetParams>,
65    ) -> Result<CallToolResult, McpError> {
66        crate_readme_get::execute(&self.state, params).await
67    }
68
69    #[tool(description = "Get high-level documentation structure from rustdoc JSON: the crate-level //! documentation (architecture overview, feature table, usage examples), module tree, and per-module item summaries. Falls back to README when docs.rs has no build yet. Primary entry point for understanding a library you're already using. Use crate_readme_get instead only when you specifically want the raw README prose.")]
70    async fn crate_docs_get(
71        &self,
72        Parameters(params): Parameters<CrateDocsGetParams>,
73    ) -> Result<CallToolResult, McpError> {
74        crate_docs_get::execute(&self.state, params).await
75    }
76
77    #[tool(description = "Search for items (types, functions, traits, methods, etc.) within a crate's API by name or concept. Returns ranked results with signatures and doc summaries. Use kind='method' to search inherent methods on types. Use after crate_docs_get to find specific items without browsing the module tree. Use crate_item_get once you know the exact fully-qualified path of the item you want.")]
78    async fn crate_item_list(
79        &self,
80        Parameters(params): Parameters<CrateItemListParams>,
81    ) -> Result<CallToolResult, McpError> {
82        crate_item_list::execute(&self.state, params).await
83    }
84
85    #[tool(description = "Get complete documentation for a specific item by fully-qualified path. Returns the full doc comment, exact type signature, generic parameters, where clauses, inherent methods, implemented traits, and feature flags. Primary API reference tool. Requires knowing the exact path — use crate_item_list first to search if you don't have it.")]
86    async fn crate_item_get(
87        &self,
88        Parameters(params): Parameters<CrateItemGetParams>,
89    ) -> Result<CallToolResult, McpError> {
90        crate_item_get::execute(&self.state, params).await
91    }
92
93    #[tool(description = "Find implementors of a trait, or all traits implemented by a type. Answers: 'what do I need to implement to use this abstraction?' and 'what can I call on this type?' Requires either trait_path (e.g. 'Default') to find types implementing that trait, or type_path (e.g. 'MyStruct') to find all traits a type implements. Use crate_item_list to discover valid type/trait names first.")]
94    async fn crate_impls_list(
95        &self,
96        Parameters(params): Parameters<CrateImplsListParams>,
97    ) -> Result<CallToolResult, McpError> {
98        crate_impls_list::execute(&self.state, params).await
99    }
100
101    #[tool(description = "List all published versions with feature maps, MSRV, dependency counts, and yank status. Use to understand release history, find when a feature was introduced, audit yanked versions, or compare features across versions.")]
102    async fn crate_versions_list(
103        &self,
104        Parameters(params): Parameters<CrateVersionsListParams>,
105    ) -> Result<CallToolResult, McpError> {
106        crate_versions_list::execute(&self.state, params).await
107    }
108
109    #[tool(description = "Get rich per-version metadata from crates.io: Rust edition, library vs binary targets, binary names, line counts, license, and publisher. Use after crate_versions_list when you need details beyond what the index provides.")]
110    async fn crate_version_get(
111        &self,
112        Parameters(params): Parameters<CrateVersionGetParams>,
113    ) -> Result<CallToolResult, McpError> {
114        crate_version_get::execute(&self.state, params).await
115    }
116
117    #[tool(description = "Get the dependency list for a crate version with semver requirements, optional flags, enabled features, and target conditions. Version defaults to latest stable. Use for due diligence: a large or unusual dependency tree is a risk multiplier.")]
118    async fn crate_dependencies_list(
119        &self,
120        Parameters(params): Parameters<CrateDependenciesListParams>,
121    ) -> Result<CallToolResult, McpError> {
122        crate_dependencies_list::execute(&self.state, params).await
123    }
124
125    #[tool(description = "List crates that depend on a given crate (reverse dependencies). Reveals ecosystem adoption breadth. A crate trusted by 5000 other crates has a different risk profile than one with 20. Use for due diligence.")]
126    async fn crate_dependents_list(
127        &self,
128        Parameters(params): Parameters<CrateDependentsListParams>,
129    ) -> Result<CallToolResult, McpError> {
130        crate_dependents_list::execute(&self.state, params).await
131    }
132
133    #[tool(description = "Get per-day download counts broken out by version for the past 90 days. Use to assess active ecosystem adoption, whether users have migrated to newer versions, and whether a download spike indicates recent adoption by a major project.")]
134    async fn crate_downloads_get(
135        &self,
136        Parameters(params): Parameters<CrateDownloadsGetParams>,
137    ) -> Result<CallToolResult, McpError> {
138        crate_downloads_get::execute(&self.state, params).await
139    }
140}
141
142#[tool_handler]
143impl ServerHandler for DocsMcpServer {
144    fn get_info(&self) -> ServerInfo {
145        ServerInfo {
146            protocol_version: ProtocolVersion::V_2024_11_05,
147            capabilities: ServerCapabilities::builder()
148                .enable_tools()
149                .build(),
150            server_info: Implementation {
151                name: "docs-mcp".to_string(),
152                version: env!("CARGO_PKG_VERSION").to_string(),
153                title: None,
154                description: Some("Rust crate documentation MCP server".to_string()),
155                icons: None,
156                website_url: None,
157            },
158            instructions: Some(
159                "This server provides accurate, up-to-date access to the Rust crate ecosystem.\n\
160                \n\
161                DISCOVERY WORKFLOW: crate_list → crate_get → crate_readme_get\n\
162                UNDERSTANDING WORKFLOW: crate_docs_get → crate_item_list → crate_item_get → crate_impls_list\n\
163                DUE DILIGENCE: crate_versions_list → crate_downloads_get → crate_dependents_list → crate_dependencies_list\n\
164                \n\
165                Tool selection guide:\n\
166                - crate_docs_get: structured docs + module tree (falls back to README if no docs.rs build)\n\
167                - crate_readme_get: raw README prose only\n\
168                - crate_item_list: search items by name/concept when you don't have the exact path\n\
169                - crate_item_get: full item details when you have the exact fully-qualified path\n\
170                - crate_impls_list: requires trait_path OR type_path (use crate_item_list to find names)\n\
171                \n\
172                All tools default to the latest stable version when version is not specified.".to_string()
173            ),
174        }
175    }
176}