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}