Skip to main content

docs_mcp/tools/
crate_docs_get.rs

1use 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    /// Crate name
13    pub name: String,
14    /// Version string. Defaults to latest stable.
15    pub version: Option<String>,
16    /// Include item-level summaries per module (default: false)
17    pub include_items: Option<bool>,
18}
19
20pub async fn execute(state: &AppState, params: CrateDocsGetParams) -> Result<CallToolResult, ErrorData> {
21    let name = &params.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    // Parallel: fetch docs.rs JSON + sparse index features
26    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            // Fall back to README; features are still available from the sparse index.
39            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    // Get root docs
58    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    // Build module tree
65    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}