biblion/
main.rs

1// Allow dead code for API completeness — fields/methods used in tests or
2// reserved for future use (e.g., BbtDb::all_citekeys, ZoteroWebClient::children).
3#![allow(dead_code)]
4
5//! Zotero MCP Server — high-performance Rust implementation.
6//!
7//! # Transports
8//!
9//! - **stdio** (default) — pipe-based, launched by Claude Code CLI.
10//! - **SSE** — HTTP daemon for Claude Desktop. Set `ZOTERO_MCP_TRANSPORT=sse`.
11//!
12//! # Subcommands
13//!
14//! - `biblion` — run MCP server (default)
15//! - `biblion check` — print diagnostics and exit
16
17mod api;
18mod config;
19mod db;
20mod protocol;
21mod server;
22mod sse;
23#[cfg(test)]
24mod test_helpers;
25mod tools;
26
27use anyhow::Result;
28
29fn main() -> Result<()> {
30    // Check for "check" subcommand
31    let args: Vec<String> = std::env::args().collect();
32    if args.len() > 1 && args[1] == "check" {
33        return run_check();
34    }
35
36    let config = config::Config::from_env();
37    let db = db::DbPool::open(&config.zotero_sqlite_path, &config.bbt_migrated_path);
38
39    let transport = std::env::var("ZOTERO_MCP_TRANSPORT").unwrap_or_else(|_| "stdio".into());
40
41    match transport.as_str() {
42        "sse" => {
43            let host = std::env::var("ZOTERO_MCP_HOST").unwrap_or_else(|_| "127.0.0.1".into());
44            let port: u16 = std::env::var("ZOTERO_MCP_PORT")
45                .unwrap_or_else(|_| "23120".into())
46                .parse()
47                .unwrap_or(23120);
48            let ctx = server::ServerContext { db, config };
49            sse::run_sse(ctx, &host, port)
50        }
51        _ => {
52            let ctx = server::ServerContext { db, config };
53            server::run_stdio(&ctx)
54        }
55    }
56}
57
58/// Diagnostic subcommand: print library info and exit.
59fn run_check() -> Result<()> {
60    let config = config::Config::from_env();
61    let db = db::DbPool::open(&config.zotero_sqlite_path, &config.bbt_migrated_path);
62
63    println!("biblion v{}", env!("CARGO_PKG_VERSION"));
64    println!();
65
66    // Zotero database
67    match &db.zotero {
68        Some(zdb) => {
69            let size = std::fs::metadata(&config.zotero_sqlite_path)
70                .map(|m| m.len() / 1_048_576)
71                .unwrap_or(0);
72            let items = zdb.item_count().unwrap_or(0);
73            let colls = zdb.collection_count().unwrap_or(0);
74            println!(
75                "Zotero database: {} ({} MB, {} items, {} collections)",
76                config.zotero_sqlite_path.display(),
77                size,
78                items,
79                colls
80            );
81        }
82        None => {
83            println!(
84                "Zotero database: NOT FOUND at {}",
85                config.zotero_sqlite_path.display()
86            );
87        }
88    }
89
90    // BBT database
91    match &db.bbt {
92        Some(bbt) => {
93            let count = bbt.all_citekeys().map(|m| m.len()).unwrap_or(0);
94            println!(
95                "BBT database:    {} ({} citekeys)",
96                config.bbt_migrated_path.display(),
97                count
98            );
99        }
100        None => {
101            println!(
102                "BBT database:    NOT FOUND at {}",
103                config.bbt_migrated_path.display()
104            );
105        }
106    }
107
108    // Write access
109    println!();
110    if config.writes_enabled && config.has_write_access() {
111        println!("Write access:    enabled (API key set, writes enabled)");
112    } else if config.has_write_access() {
113        println!("Write access:    disabled (API key set, but ZOTERO_MCP_ENABLE_WRITES not set)");
114    } else {
115        println!("Write access:    disabled (no ZOTERO_API_KEY)");
116    }
117
118    // Transport
119    let transport = std::env::var("ZOTERO_MCP_TRANSPORT").unwrap_or_else(|_| "stdio".into());
120    println!("Transport:       {transport}");
121
122    Ok(())
123}