docs_mcp/tools/
crate_list.rs1use rmcp::{ErrorData, model::CallToolResult};
2use rmcp::model::Content;
3use serde::{Deserialize, Serialize};
4use rmcp::schemars::{self, JsonSchema};
5
6use crate::cratesio::CrateInfo;
7use super::AppState;
8
9#[derive(Serialize)]
10struct CrateListEntry<'a> {
11 name: &'a str,
12 description: Option<&'a str>,
13 version: Option<&'a str>,
14 newest_version: Option<&'a str>,
15 downloads: u64,
16 recent_downloads: Option<u64>,
17 updated_at: &'a str,
18 repository: Option<&'a str>,
19}
20
21impl<'a> From<&'a CrateInfo> for CrateListEntry<'a> {
22 fn from(c: &'a CrateInfo) -> Self {
23 Self {
24 name: &c.name,
25 description: c.description.as_deref(),
26 version: c.max_stable_version.as_deref().or(c.max_version.as_deref()),
27 newest_version: c.newest_version.as_deref(),
28 downloads: c.downloads,
29 recent_downloads: c.recent_downloads,
30 updated_at: &c.updated_at,
31 repository: c.repository.as_deref(),
32 }
33 }
34}
35
36#[derive(Debug, Deserialize, JsonSchema)]
37pub struct CrateListParams {
38 pub query: Option<String>,
40 pub category: Option<String>,
42 pub keyword: Option<String>,
44 pub sort: Option<String>,
46 pub page: Option<u32>,
48 pub per_page: Option<u32>,
50}
51
52pub async fn execute(state: &AppState, params: CrateListParams) -> Result<CallToolResult, ErrorData> {
53 let query = params.query.as_deref().unwrap_or("");
54 let page = params.page.unwrap_or(1).max(1);
55 let per_page = params.per_page.unwrap_or(10).min(100);
56
57 let client = crate::cratesio::CratesIoClient::new(&state.client, &state.cache);
58 let result = client
59 .search(
60 query,
61 params.category.as_deref(),
62 params.keyword.as_deref(),
63 params.sort.as_deref(),
64 page,
65 per_page,
66 )
67 .await
68 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
69
70 let entries: Vec<CrateListEntry> = result.crates.iter().map(CrateListEntry::from).collect();
71 let output = serde_json::json!({ "crates": entries, "total": result.meta.total });
72 let json = serde_json::to_string_pretty(&output)
73 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
74
75 Ok(CallToolResult::success(vec![Content::text(json)]))
76}