Skip to main content

docs_mcp/tools/
crate_get.rs

1use rmcp::{ErrorData, model::{CallToolResult, Content}};
2use serde::{Deserialize, Serialize};
3use rmcp::schemars::{self, JsonSchema};
4
5use super::AppState;
6
7#[derive(Serialize)]
8struct CrateGetOutput<'a> {
9    name: &'a str,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    description: Option<&'a str>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    homepage: Option<&'a str>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    documentation: Option<&'a str>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    repository: Option<&'a str>,
18    downloads: u64,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    recent_downloads: Option<u64>,
21    updated_at: &'a str,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    max_stable_version: Option<&'a str>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    max_version: Option<&'a str>,
26    features: std::collections::HashMap<String, Vec<String>>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    keywords: Option<Vec<&'a str>>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    categories: Option<Vec<&'a str>>,
31}
32
33#[derive(Debug, Deserialize, JsonSchema)]
34pub struct CrateGetParams {
35    /// Exact crate name (e.g. "serde")
36    pub name: String,
37}
38
39pub async fn execute(state: &AppState, params: CrateGetParams) -> Result<CallToolResult, ErrorData> {
40    let name = &params.name;
41    let client = crate::cratesio::CratesIoClient::new(&state.client, &state.cache);
42
43    // Parallel: crates.io API + sparse index
44    let (api_result, index_result) = tokio::join!(
45        client.get_crate(name),
46        state.fetch_index(name)
47    );
48
49    let api = api_result.map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
50    let index_lines = index_result.map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
51
52    // Find latest stable from sparse index
53    let latest_stable = crate::sparse_index::find_latest_stable(&index_lines);
54    let features = latest_stable.map(|l| l.all_features()).unwrap_or_default();
55
56    let krate = &api.krate;
57    let output = CrateGetOutput {
58        name: &krate.name,
59        description: krate.description.as_deref(),
60        homepage: krate.homepage.as_deref(),
61        documentation: krate.documentation.as_deref(),
62        repository: krate.repository.as_deref(),
63        downloads: krate.downloads,
64        recent_downloads: krate.recent_downloads,
65        updated_at: &krate.updated_at,
66        max_stable_version: krate.max_stable_version.as_deref(),
67        max_version: krate.max_version.as_deref(),
68        features,
69        keywords: api.keywords.as_ref().map(|kws| kws.iter().map(|k| k.keyword.as_str()).collect()),
70        categories: api.categories.as_ref().map(|cats| cats.iter().map(|c| c.category.as_str()).collect()),
71    };
72
73    let json = serde_json::to_string_pretty(&output)
74        .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
75
76    Ok(CallToolResult::success(vec![Content::text(json)]))
77}