@@ -83,9 +83,13 @@ impl DocRouter {
8383 } ;
8484
8585 // Fetch the documentation page
86- let response = self . client . get ( & url) . send ( ) . await . map_err ( |e| {
87- ToolError :: ExecutionError ( format ! ( "Failed to fetch documentation: {}" , e) )
88- } ) ?;
86+ let response = self . client . get ( & url)
87+ . header ( "User-Agent" , "CrateDocs/0.1.0 (https://github.com/d6e/cratedocs-mcp)" )
88+ . send ( )
89+ . await
90+ . map_err ( |e| {
91+ ToolError :: ExecutionError ( format ! ( "Failed to fetch documentation: {}" , e) )
92+ } ) ?;
8993
9094 if !response. status ( ) . is_success ( ) {
9195 return Err ( ToolError :: ExecutionError ( format ! (
@@ -113,9 +117,13 @@ impl DocRouter {
113117
114118 let url = format ! ( "https://crates.io/api/v1/crates?q={}&per_page={}" , query, limit) ;
115119
116- let response = self . client . get ( & url) . send ( ) . await . map_err ( |e| {
117- ToolError :: ExecutionError ( format ! ( "Failed to search crates.io: {}" , e) )
118- } ) ?;
120+ let response = self . client . get ( & url)
121+ . header ( "User-Agent" , "CrateDocs/0.1.0 (https://github.com/d6e/cratedocs-mcp)" )
122+ . send ( )
123+ . await
124+ . map_err ( |e| {
125+ ToolError :: ExecutionError ( format ! ( "Failed to search crates.io: {}" , e) )
126+ } ) ?;
119127
120128 if !response. status ( ) . is_success ( ) {
121129 return Err ( ToolError :: ExecutionError ( format ! (
@@ -151,36 +159,78 @@ impl DocRouter {
151159 return Ok ( doc) ;
152160 }
153161
154- // Construct the docs.rs URL for the specific item
155- let url = if let Some ( ver) = version {
156- format ! ( "https://docs.rs/{}/{}/{}/" , crate_name, ver, item_path. replace( "::" , "/" ) )
162+ // Process the item path to determine the item type
163+ // Format: module::path::ItemName
164+ // Need to split into module path and item name, and guess item type
165+ let parts: Vec < & str > = item_path. split ( "::" ) . collect ( ) ;
166+
167+ if parts. is_empty ( ) {
168+ return Err ( ToolError :: InvalidParameters (
169+ "Invalid item path. Expected format: module::path::ItemName" . to_string ( )
170+ ) ) ;
171+ }
172+
173+ let item_name = parts. last ( ) . unwrap ( ) . to_string ( ) ;
174+ let module_path = if parts. len ( ) > 1 {
175+ parts[ ..parts. len ( ) -1 ] . join ( "/" )
157176 } else {
158- format ! ( "https://docs.rs/{}/latest/{}/" , crate_name , item_path . replace ( "::" , "/" ) )
177+ String :: new ( )
159178 } ;
160-
161- // Fetch the documentation page
162- let response = self . client . get ( & url) . send ( ) . await . map_err ( |e| {
163- ToolError :: ExecutionError ( format ! ( "Failed to fetch item documentation: {}" , e) )
164- } ) ?;
165-
166- if !response. status ( ) . is_success ( ) {
167- return Err ( ToolError :: ExecutionError ( format ! (
168- "Failed to fetch item documentation. Status: {}" ,
169- response. status( )
170- ) ) ) ;
171- }
172-
173- let html_body = response. text ( ) . await . map_err ( |e| {
174- ToolError :: ExecutionError ( format ! ( "Failed to read response body: {}" , e) )
175- } ) ?;
176179
177- // Convert HTML to markdown
178- let markdown_body = parse_html ( & html_body) ;
179-
180- // Cache the markdown result
181- self . cache . set ( cache_key, markdown_body. clone ( ) ) . await ;
180+ // Try different item types (struct, enum, trait, fn)
181+ let item_types = [ "struct" , "enum" , "trait" , "fn" , "macro" ] ;
182+ let mut last_error = None ;
182183
183- Ok ( markdown_body)
184+ for item_type in item_types. iter ( ) {
185+ // Construct the docs.rs URL for the specific item
186+ let url = if let Some ( ver) = version. clone ( ) {
187+ if module_path. is_empty ( ) {
188+ format ! ( "https://docs.rs/{}/{}/{}/{}.{}.html" , crate_name, ver, crate_name, item_type, item_name)
189+ } else {
190+ format ! ( "https://docs.rs/{}/{}/{}/{}/{}.{}.html" , crate_name, ver, crate_name, module_path, item_type, item_name)
191+ }
192+ } else {
193+ if module_path. is_empty ( ) {
194+ format ! ( "https://docs.rs/{}/latest/{}/{}.{}.html" , crate_name, crate_name, item_type, item_name)
195+ } else {
196+ format ! ( "https://docs.rs/{}/latest/{}/{}/{}.{}.html" , crate_name, crate_name, module_path, item_type, item_name)
197+ }
198+ } ;
199+
200+ // Try to fetch the documentation page
201+ let response = match self . client . get ( & url)
202+ . header ( "User-Agent" , "CrateDocs/0.1.0 (https://github.com/d6e/cratedocs-mcp)" )
203+ . send ( ) . await {
204+ Ok ( resp) => resp,
205+ Err ( e) => {
206+ last_error = Some ( e. to_string ( ) ) ;
207+ continue ;
208+ }
209+ } ;
210+
211+ // If found, process and return
212+ if response. status ( ) . is_success ( ) {
213+ let html_body = response. text ( ) . await . map_err ( |e| {
214+ ToolError :: ExecutionError ( format ! ( "Failed to read response body: {}" , e) )
215+ } ) ?;
216+
217+ // Convert HTML to markdown
218+ let markdown_body = parse_html ( & html_body) ;
219+
220+ // Cache the markdown result
221+ self . cache . set ( cache_key, markdown_body. clone ( ) ) . await ;
222+
223+ return Ok ( markdown_body) ;
224+ }
225+
226+ last_error = Some ( format ! ( "Status code: {}" , response. status( ) ) ) ;
227+ }
228+
229+ // If we got here, none of the item types worked
230+ Err ( ToolError :: ExecutionError ( format ! (
231+ "Failed to fetch item documentation. No matching item found. Last error: {}" ,
232+ last_error. unwrap_or_else( || "Unknown error" . to_string( ) )
233+ ) ) )
184234 }
185235}
186236
0 commit comments