use std::sync::Arc;
use schemars::JsonSchema;
use serde::Deserialize;
use tower_mcp::{
CallToolResult, ResultExt, Tool, ToolBuilder,
extract::{Json, State},
};
use crate::docs::format;
use crate::state::AppState;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetDocItemInput {
name: String,
#[serde(default = "default_version")]
version: String,
item_path: String,
}
fn default_version() -> String {
"latest".to_string()
}
pub fn build(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_doc_item")
.title("Get Doc Item")
.description(
"Get full documentation for a specific item (function, struct, trait, etc.) \
from docs.rs. Includes the item's signature, doc comments, and for structs, \
the list of public methods.",
)
.read_only()
.extractor_handler(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetDocItemInput>| async move {
let krate = state
.docs_cache
.get_or_fetch(&state.docsrs_client, &input.name, &input.version)
.await
.tool_context("docs.rs fetch error")?;
let item =
format::resolve_item_path(&krate, &input.item_path).ok_or_else(|| {
tower_mcp::ToolError::new(format!(
"Item '{}' not found in {} v{}",
input.item_path, input.name, input.version
))
})?;
let output = format::format_item_detail(&krate, item);
Ok(CallToolResult::text(output))
},
)
.build()
}