Skip to content

Commit f50ac58

Browse files
author
Danielle Jenkins
committed
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
1 parent ebede72 commit f50ac58

File tree

2 files changed

+91
-34
lines changed

2 files changed

+91
-34
lines changed

src/bin/cratedocs.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,14 @@ async fn run_test_tool(
159159
println!("Usage examples:");
160160
println!(" cargo run --bin cratedocs -- test --tool lookup_crate --crate-name serde");
161161
println!(" cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --version 1.35.0");
162+
println!(" cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender");
163+
println!(" cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147");
162164
println!(" cargo run --bin cratedocs -- test --tool search_crates --query logger\n");
163165
println!("Available tools:");
164166
println!(" lookup_crate - Look up documentation for a Rust crate");
165167
println!(" lookup_item - Look up documentation for a specific item in a crate");
168+
println!(" Format: 'module::path::ItemName' (e.g., 'sync::mpsc::Sender')");
169+
println!(" The tool will try to detect if it's a struct, enum, trait, fn, or macro");
166170
println!(" search_crates - Search for crates on crates.io");
167171
println!(" help - Show this help information\n");
168172
return Ok(());
@@ -224,9 +228,12 @@ async fn run_test_tool(
224228
Ok(result) => result,
225229
Err(e) => {
226230
eprintln!("\nERROR: {}", e);
227-
eprintln!("\nTip: The direct item lookup may require very specific path formats. Try these commands instead:");
231+
eprintln!("\nTip: Try these suggestions:");
228232
eprintln!(" - For crate docs: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio");
229-
eprintln!(" - For crate docs with version: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name serde --version 1.0.147");
233+
eprintln!(" - For item lookup: cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender");
234+
eprintln!(" - For item lookup with version: cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147");
235+
eprintln!(" - For crate search: cargo run --bin cratedocs -- test --tool search_crates --query logger --limit 5");
236+
eprintln!(" - For help: cargo run --bin cratedocs -- test --tool help");
230237
return Ok(());
231238
}
232239
};

src/tools/docs/docs.rs

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)