biblion/tools/
paper.rs

1//! Paper search MCP tools — expose paper-resolver as standalone tools.
2//!
3//! These tools let Claude find open-access PDFs without needing a specific
4//! Zotero item. Useful for: "find me the PDF for this DOI", "is this paper
5//! available open-access?", "what sources are configured?"
6
7use serde_json::Value;
8
9use crate::protocol::ToolCallResult;
10use crate::server::ServerContext;
11
12/// Find an open-access PDF URL for a paper.
13///
14/// Queries all enabled sources concurrently and returns the best result.
15pub fn paper_resolve_pdf(args: &Value, ctx: &ServerContext) -> ToolCallResult {
16    let doi = args.get("doi").and_then(|v| v.as_str());
17    let title = args.get("title").and_then(|v| v.as_str());
18    let url = args.get("url").and_then(|v| v.as_str());
19
20    if doi.is_none() && title.is_none() && url.is_none() {
21        return ToolCallResult::error("Provide at least one of: doi, title, url".into());
22    }
23
24    let report = paper_resolver::resolve_pdf_with_report(doi, url, title, &ctx.config.resolver);
25
26    ToolCallResult::text(report.summary())
27}
28
29/// Show the current paper resolver configuration.
30///
31/// Lists enabled/disabled sources, their priority order, timeout, and email.
32pub fn paper_source_status(ctx: &ServerContext) -> ToolCallResult {
33    let config = &ctx.config.resolver;
34    let mut output = String::from("Paper Resolver Configuration\n\n");
35
36    output.push_str(&format!("Email: {}\n", config.email));
37    output.push_str(&format!("User-Agent: {}\n", config.user_agent));
38    output.push_str(&format!("Timeout: {}s\n", config.timeout_secs));
39    output.push_str(&format!(
40        "Extra blocked domains: {}\n\n",
41        if config.extra_blocked_domains.is_empty() {
42            "(none)".into()
43        } else {
44            config.extra_blocked_domains.join(", ")
45        }
46    ));
47
48    output.push_str("Sources (priority order):\n");
49    for (i, source) in config.sources.iter().enumerate() {
50        let status = if source.enabled {
51            "enabled"
52        } else {
53            "disabled"
54        };
55        output.push_str(&format!("  {}. {} [{}]\n", i + 1, source.name, status));
56    }
57
58    ToolCallResult::text(output)
59}