Improve tools
1. Fixed the lookup_item command to properly handle item paths by:
    - Trying different item types (struct, enum, trait, fn, macro)
    - Using the proper URL structure for docs.rs items
    - Adding User-Agent headers to avoid 404 errors
  2. Fixed the search_crates command to:
    - Include proper User-Agent headers to avoid 403 Forbidden errors
    - Return JSON data in a consistent format
  3. Made general improvements:
    - Better CLI help text with examples
    - Better error messages with specific examples
    - More comprehensive documentation of usage patterns
			
			
This commit is contained in:
		
							parent
							
								
									ebede724ad
								
							
						
					
					
						commit
						f50ac58a24
					
				
					 2 changed files with 92 additions and 35 deletions
				
			
		|  | @ -159,10 +159,14 @@ async fn run_test_tool( | |||
|         println!("Usage examples:"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_crate --crate-name serde"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --version 1.35.0"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool search_crates --query logger\n"); | ||||
|         println!("Available tools:"); | ||||
|         println!("  lookup_crate   - Look up documentation for a Rust crate"); | ||||
|         println!("  lookup_item    - Look up documentation for a specific item in a crate"); | ||||
|         println!("                   Format: 'module::path::ItemName' (e.g., 'sync::mpsc::Sender')"); | ||||
|         println!("                   The tool will try to detect if it's a struct, enum, trait, fn, or macro"); | ||||
|         println!("  search_crates  - Search for crates on crates.io"); | ||||
|         println!("  help           - Show this help information\n"); | ||||
|         return Ok(()); | ||||
|  | @ -224,9 +228,12 @@ async fn run_test_tool( | |||
|         Ok(result) => result, | ||||
|         Err(e) => { | ||||
|             eprintln!("\nERROR: {}", e); | ||||
|             eprintln!("\nTip: The direct item lookup may require very specific path formats. Try these commands instead:"); | ||||
|             eprintln!("\nTip: Try these suggestions:"); | ||||
|             eprintln!("  - For crate docs: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio"); | ||||
|             eprintln!("  - For crate docs with version: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name serde --version 1.0.147"); | ||||
|             eprintln!("  - For item lookup: cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender"); | ||||
|             eprintln!("  - For item lookup with version: cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147"); | ||||
|             eprintln!("  - For crate search: cargo run --bin cratedocs -- test --tool search_crates --query logger --limit 5"); | ||||
|             eprintln!("  - For help: cargo run --bin cratedocs -- test --tool help"); | ||||
|             return Ok(()); | ||||
|         } | ||||
|     }; | ||||
|  |  | |||
|  | @ -83,9 +83,13 @@ impl DocRouter { | |||
|         }; | ||||
| 
 | ||||
|         // Fetch the documentation page
 | ||||
|         let response = self.client.get(&url).send().await.map_err(|e| { | ||||
|             ToolError::ExecutionError(format!("Failed to fetch documentation: {}", e)) | ||||
|         })?; | ||||
|         let response = self.client.get(&url) | ||||
|             .header("User-Agent", "CrateDocs/0.1.0 (https://github.com/d6e/cratedocs-mcp)") | ||||
|             .send() | ||||
|             .await | ||||
|             .map_err(|e| { | ||||
|                 ToolError::ExecutionError(format!("Failed to fetch documentation: {}", e)) | ||||
|             })?; | ||||
| 
 | ||||
|         if !response.status().is_success() { | ||||
|             return Err(ToolError::ExecutionError(format!( | ||||
|  | @ -113,9 +117,13 @@ impl DocRouter { | |||
|         
 | ||||
|         let url = format!("https://crates.io/api/v1/crates?q={}&per_page={}", query, limit); | ||||
|         
 | ||||
|         let response = self.client.get(&url).send().await.map_err(|e| { | ||||
|             ToolError::ExecutionError(format!("Failed to search crates.io: {}", e)) | ||||
|         })?; | ||||
|         let response = self.client.get(&url) | ||||
|             .header("User-Agent", "CrateDocs/0.1.0 (https://github.com/d6e/cratedocs-mcp)") | ||||
|             .send() | ||||
|             .await | ||||
|             .map_err(|e| { | ||||
|                 ToolError::ExecutionError(format!("Failed to search crates.io: {}", e)) | ||||
|             })?; | ||||
| 
 | ||||
|         if !response.status().is_success() { | ||||
|             return Err(ToolError::ExecutionError(format!( | ||||
|  | @ -151,36 +159,78 @@ impl DocRouter { | |||
|             return Ok(doc); | ||||
|         } | ||||
| 
 | ||||
|         // Construct the docs.rs URL for the specific item
 | ||||
|         let url = if let Some(ver) = version { | ||||
|             format!("https://docs.rs/{}/{}/{}/", crate_name, ver, item_path.replace("::", "/")) | ||||
|         } else { | ||||
|             format!("https://docs.rs/{}/latest/{}/", crate_name, item_path.replace("::", "/")) | ||||
|         }; | ||||
| 
 | ||||
|         // Fetch the documentation page
 | ||||
|         let response = self.client.get(&url).send().await.map_err(|e| { | ||||
|             ToolError::ExecutionError(format!("Failed to fetch item documentation: {}", e)) | ||||
|         })?; | ||||
| 
 | ||||
|         if !response.status().is_success() { | ||||
|             return Err(ToolError::ExecutionError(format!( | ||||
|                 "Failed to fetch item documentation. Status: {}", | ||||
|                 response.status() | ||||
|             ))); | ||||
|         // Process the item path to determine the item type
 | ||||
|         // Format: module::path::ItemName
 | ||||
|         // Need to split into module path and item name, and guess item type
 | ||||
|         let parts: Vec<&str> = item_path.split("::").collect(); | ||||
|         
 | ||||
|         if parts.is_empty() { | ||||
|             return Err(ToolError::InvalidParameters( | ||||
|                 "Invalid item path. Expected format: module::path::ItemName".to_string() | ||||
|             )); | ||||
|         } | ||||
| 
 | ||||
|         let html_body = response.text().await.map_err(|e| { | ||||
|             ToolError::ExecutionError(format!("Failed to read response body: {}", e)) | ||||
|         })?; | ||||
|         
 | ||||
|         // Convert HTML to markdown
 | ||||
|         let markdown_body = parse_html(&html_body); | ||||
| 
 | ||||
|         // Cache the markdown result
 | ||||
|         self.cache.set(cache_key, markdown_body.clone()).await; | ||||
|         let item_name = parts.last().unwrap().to_string(); | ||||
|         let module_path = if parts.len() > 1 { | ||||
|             parts[..parts.len()-1].join("/") | ||||
|         } else { | ||||
|             String::new() | ||||
|         }; | ||||
|         
 | ||||
|         Ok(markdown_body) | ||||
|         // Try different item types (struct, enum, trait, fn)
 | ||||
|         let item_types = ["struct", "enum", "trait", "fn", "macro"]; | ||||
|         let mut last_error = None; | ||||
|         
 | ||||
|         for item_type in item_types.iter() { | ||||
|             // Construct the docs.rs URL for the specific item
 | ||||
|             let url = if let Some(ver) = version.clone() { | ||||
|                 if module_path.is_empty() { | ||||
|                     format!("https://docs.rs/{}/{}/{}/{}.{}.html", crate_name, ver, crate_name, item_type, item_name) | ||||
|                 } else { | ||||
|                     format!("https://docs.rs/{}/{}/{}/{}/{}.{}.html", crate_name, ver, crate_name, module_path, item_type, item_name) | ||||
|                 } | ||||
|             } else { | ||||
|                 if module_path.is_empty() { | ||||
|                     format!("https://docs.rs/{}/latest/{}/{}.{}.html", crate_name, crate_name, item_type, item_name) | ||||
|                 } else { | ||||
|                     format!("https://docs.rs/{}/latest/{}/{}/{}.{}.html", crate_name, crate_name, module_path, item_type, item_name) | ||||
|                 } | ||||
|             }; | ||||
|             
 | ||||
|             // Try to fetch the documentation page
 | ||||
|             let response = match self.client.get(&url) | ||||
|                 .header("User-Agent", "CrateDocs/0.1.0 (https://github.com/d6e/cratedocs-mcp)") | ||||
|                 .send().await { | ||||
|                 Ok(resp) => resp, | ||||
|                 Err(e) => { | ||||
|                     last_error = Some(e.to_string()); | ||||
|                     continue; | ||||
|                 } | ||||
|             }; | ||||
|             
 | ||||
|             // If found, process and return
 | ||||
|             if response.status().is_success() { | ||||
|                 let html_body = response.text().await.map_err(|e| { | ||||
|                     ToolError::ExecutionError(format!("Failed to read response body: {}", e)) | ||||
|                 })?; | ||||
|                 
 | ||||
|                 // Convert HTML to markdown
 | ||||
|                 let markdown_body = parse_html(&html_body); | ||||
|                 
 | ||||
|                 // Cache the markdown result
 | ||||
|                 self.cache.set(cache_key, markdown_body.clone()).await; | ||||
|                 
 | ||||
|                 return Ok(markdown_body); | ||||
|             } | ||||
|             
 | ||||
|             last_error = Some(format!("Status code: {}", response.status())); | ||||
|         } | ||||
|         
 | ||||
|         // If we got here, none of the item types worked
 | ||||
|         Err(ToolError::ExecutionError(format!( | ||||
|             "Failed to fetch item documentation. No matching item found. Last error: {}", | ||||
|             last_error.unwrap_or_else(|| "Unknown error".to_string()) | ||||
|         ))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Danielle Jenkins
						Danielle Jenkins