#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::indexing_slicing)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
use std::path::PathBuf;
use std::process::ExitCode;
use inillucent_cli::mcp::{self, Settings};
#[global_allocator]
static ALLOCATOR: inillucent_alloc::Pooled = inillucent_alloc::Pooled;
fn main() -> ExitCode {
inillucent_cli::on_a_sized_stack(run)
}
fn run() -> ExitCode {
let arguments: Vec<String> = std::env::args().skip(1).collect();
if arguments
.iter()
.any(|word| word == "--help" || word == "-h")
{
usage();
return ExitCode::SUCCESS;
}
if arguments
.iter()
.any(|word| word == "--version" || word == "-V")
{
println!("inillucent-mcp {}", env!("CARGO_PKG_VERSION"));
return ExitCode::SUCCESS;
}
let settings = match parse(&arguments) {
Ok(settings) => settings,
Err(message) => {
eprintln!("inillucent-mcp: {message}");
return ExitCode::from(2);
}
};
let input = std::io::BufReader::new(std::io::stdin());
let mut output = std::io::stdout();
match mcp::serve(settings, input, &mut output) {
Ok(()) => ExitCode::SUCCESS,
Err(message) => {
eprintln!("inillucent-mcp: {message}");
ExitCode::FAILURE
}
}
}
fn parse(arguments: &[String]) -> Result<Settings, String> {
let mut settings = Settings {
database: std::env::var("INILLUCENT_DB").unwrap_or_else(|_| ":memory:".to_string()),
..Settings::default()
};
let mut walk = arguments.iter();
while let Some(argument) = walk.next() {
match argument.as_str() {
"--db" | "-d" => {
settings.database = walk
.next()
.cloned()
.ok_or_else(|| "--db needs a path.".to_string())?;
}
"--readonly" => settings.readonly = true,
"--root" => {
let named = walk
.next()
.cloned()
.ok_or_else(|| "--root needs a directory.".to_string())?;
settings.root = Some(PathBuf::from(named));
}
"--limit" => {
let value = walk
.next()
.cloned()
.ok_or_else(|| "--limit needs a number.".to_string())?;
settings.limit = value
.parse()
.map_err(|_| format!("--limit wants a number, not '{value}'."))?;
}
other => return Err(format!("'{other}' is not an option this server takes.")),
}
}
Ok(settings)
}
fn usage() {
println!("inillucent-mcp - serve inillucent's commands to an agent over MCP (stdio)");
println!();
println!("Usage: inillucent-mcp [options]");
println!();
for line in [
" -d, --db PATH the database to serve (or $INILLUCENT_DB; :memory: by default)",
" --readonly refuse every statement that would change something",
" --root DIR refuse every path that resolves outside DIR (links followed)",
" --limit N how many rows a call gets back when it does not say (default 200)",
" -V, --version print the version and stop",
" -h, --help print this",
] {
println!("{line}");
}
println!();
println!("It speaks JSON-RPC 2.0 over standard input and output, one object per line.");
println!("Configure it in an MCP client as:");
println!(" {{\"command\": [\"inillucent-mcp\", \"--db\", \"app.rdb\"]}}");
}