docs_mcp/tools/
crate_docs_get.rs1use rmcp::{ErrorData, model::{CallToolResult, Content}};
2use serde::Deserialize;
3use rmcp::schemars::{self, JsonSchema};
4use serde_json::json;
5
6use super::AppState;
7use crate::docsrs::{fetch_rustdoc_json, build_module_tree, ModuleNode, ItemSummary};
8use crate::sparse_index::find_latest_stable;
9
10#[derive(Debug, Deserialize, JsonSchema)]
11pub struct CrateDocsGetParams {
12 pub name: String,
14 pub version: Option<String>,
16 pub include_items: Option<bool>,
18}
19
20pub async fn execute(state: &AppState, params: CrateDocsGetParams) -> Result<CallToolResult, ErrorData> {
21 let name = ¶ms.name;
22 let version = state.resolve_version(name, params.version.as_deref()).await
23 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
24
25 let (docs_result, index_result) = tokio::join!(
27 fetch_rustdoc_json(name, &version, &state.client, &state.cache),
28 state.fetch_index(name)
29 );
30
31 let index_lines = index_result.unwrap_or_default();
32 let latest = find_latest_stable(&index_lines);
33 let features = latest.map(|l| l.all_features()).unwrap_or_default();
34
35 let doc = match docs_result {
36 Ok(d) => d,
37 Err(crate::error::DocsError::DocsNotFound { .. }) => {
38 let client = crate::cratesio::CratesIoClient::new(&state.client, &state.cache);
40 let readme = client.get_readme(name, &version).await
41 .unwrap_or_else(|_| "No documentation available".to_string());
42 let output = json!({
43 "name": name,
44 "version": version,
45 "root_docs": readme,
46 "note": "docs.rs build not available; showing README instead",
47 "module_tree": [],
48 "features": features,
49 });
50 let json = serde_json::to_string_pretty(&output)
51 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
52 return Ok(CallToolResult::success(vec![Content::text(json)]));
53 }
54 Err(e) => return Err(ErrorData::internal_error(e.to_string(), None)),
55 };
56
57 let root_item = doc.index.get(&doc.root_id());
59 let root_docs = root_item
60 .and_then(|i| i.docs.as_deref())
61 .unwrap_or("")
62 .to_string();
63
64 let module_tree = build_module_tree(&doc);
66 let tree_json = serialize_module_nodes(&module_tree, params.include_items.unwrap_or(false));
67
68 let output = json!({
69 "name": name,
70 "version": version,
71 "format_version": doc.format_version,
72 "root_docs": root_docs,
73 "features": features,
74 "module_tree": tree_json,
75 });
76
77 let json = serde_json::to_string_pretty(&output)
78 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
79
80 Ok(CallToolResult::success(vec![Content::text(json)]))
81}
82
83fn serialize_item_summary(s: &ItemSummary) -> serde_json::Value {
84 json!({
85 "kind": s.kind,
86 "name": s.name,
87 "doc_summary": s.doc_summary,
88 })
89}
90
91fn serialize_module_nodes(nodes: &[ModuleNode], include_items: bool) -> serde_json::Value {
92 let arr: Vec<serde_json::Value> = nodes.iter().map(|n| {
93 let mut obj = json!({
94 "path": n.path,
95 "doc_summary": n.doc_summary,
96 "item_counts": n.item_counts,
97 });
98 if include_items && !n.items.is_empty() {
99 obj["items"] = serde_json::Value::Array(
100 n.items.iter().map(serialize_item_summary).collect()
101 );
102 }
103 if !n.children.is_empty() {
104 obj["children"] = serialize_module_nodes(&n.children, include_items);
105 }
106 obj
107 }).collect();
108 serde_json::Value::Array(arr)
109}