use std::path::PathBuf;
use anyhow::{Context, Result};
use clap::Args as ClapArgs;
use scrybe_application::agent_access::handle_message;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use crate::runtime::{application, config_service};
#[derive(ClapArgs, Debug)]
pub struct Args {
#[arg(long)]
pub root: Option<PathBuf>,
}
pub async fn run(args: Args) -> Result<()> {
let cfg = config_service()?.load()?;
if !cfg.agent_access.enabled {
anyhow::bail!(
"scrybe mcp: refusing to start — this is an opt-in, read-only surface; enable it \
first by setting `[agent_access] enabled = true` in config.toml (see README.md's \
Privacy and Network Posture section)"
);
}
let app = application(args.root.as_deref())?;
let mut lines = BufReader::new(tokio::io::stdin()).lines();
let mut stdout = tokio::io::stdout();
while let Some(line) = lines.next_line().await.context("reading stdin")? {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if let Some(response) = handle_message(app.session_reader(), trimmed) {
stdout
.write_all(response.as_bytes())
.await
.context("writing stdout")?;
stdout.write_all(b"\n").await.context("writing stdout")?;
stdout.flush().await.context("flushing stdout")?;
}
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[tokio::test]
async fn test_run_refuses_to_start_when_agent_access_disabled() {
let cfg_dir = tempfile::tempdir().unwrap();
let config_path = cfg_dir.path().join("nonexistent-config.toml");
std::env::set_var("SCRYBE_CONFIG", &config_path);
let err = run(Args { root: None }).await.unwrap_err();
std::env::remove_var("SCRYBE_CONFIG");
assert!(err.to_string().contains("refusing to start"));
}
}