use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use clap::Args;
use cortex_mcp::tool_handler::GateId;
use cortex_mcp::tool_registry::ToolRegistry;
use cortex_mcp::tools::admit_axiom::CortexAdmitAxiomTool;
use cortex_mcp::tools::audit_verify::CortexAuditVerifyTool;
use cortex_mcp::tools::config_status::CortexConfigTool;
use cortex_mcp::tools::context::CortexContextTool;
use cortex_mcp::tools::decay_status::CortexDecayStatusTool;
use cortex_mcp::tools::doctor::CortexDoctorTool;
use cortex_mcp::tools::health::CortexMemoryHealthTool;
use cortex_mcp::tools::memory_accept::CortexMemoryAcceptTool;
use cortex_mcp::tools::memory_embed::CortexMemoryEmbedTool;
use cortex_mcp::tools::memory_list::CortexMemoryListTool;
use cortex_mcp::tools::memory_outcome::CortexMemoryOutcomeTool;
use cortex_mcp::tools::models_list::CortexModelsListTool;
use cortex_mcp::tools::reflect::CortexReflectTool;
use cortex_mcp::tools::search::CortexSearchTool;
use cortex_mcp::tools::session_close::CortexSessionCloseTool;
use cortex_mcp::tools::session_commit::CortexSessionCommitTool;
use cortex_mcp::tools::suggest::CortexSuggestTool;
use crate::config::{AutoCommitSource, McpConfig};
use crate::exit::Exit;
use crate::paths::DataLayout;
#[derive(Debug, Args)]
pub struct ServeArgs {
#[arg(long, value_name = "PATH")]
pub db: Option<PathBuf>,
#[arg(long = "event-log", value_name = "PATH")]
pub event_log: Option<PathBuf>,
}
pub fn run(args: ServeArgs) -> Exit {
let layout = match DataLayout::resolve(args.db, args.event_log) {
Ok(l) => l,
Err(exit) => return exit,
};
tracing::debug!(
db = %layout.db_path.display(),
event_log = %layout.event_log_path.display(),
"cortex serve: resolved data layout"
);
if !layout.db_path.exists() {
eprintln!(
"cortex serve: precondition unmet: database {} does not exist; run `cortex init` first. no state was changed.",
layout.db_path.display()
);
return Exit::PreconditionUnmet;
}
let raw_pool = match cortex_store::Pool::open(&layout.db_path) {
Ok(p) => p,
Err(err) => {
eprintln!("cortex serve: failed to open database: {err}");
return Exit::Internal;
}
};
if let Err(err) = cortex_store::migrate::apply_pending(&raw_pool) {
eprintln!("cortex serve: failed to apply migrations: {err}");
return Exit::Internal;
}
let pool = Arc::new(Mutex::new(raw_pool));
let mcp_cfg = McpConfig::resolve();
let auto_commit_enabled = mcp_cfg.auto_commit;
if auto_commit_enabled {
let source_label = match mcp_cfg.auto_commit_source {
AutoCommitSource::EnvVar => "env var CORTEX_MCP_AUTO_COMMIT=1",
AutoCommitSource::ConfigFile => "[mcp] auto_commit = true in config file",
AutoCommitSource::NotSet => "unknown source",
};
eprintln!(
"cortex serve: WARNING: session auto-commit is ENABLED (source: {source_label})"
);
eprintln!(
"cortex serve: WARNING: ADR 0047 operator-confirmation token is BYPASSED — \
every session will self-commit without operator input"
);
eprintln!(
"cortex serve: WARNING: to disable, unset CORTEX_MCP_AUTO_COMMIT or set \
[mcp] auto_commit = false in cortex.toml"
);
}
let token = confirmation_token();
eprintln!("cortex serve: confirmation token: {token}");
let session_token: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(Some(token)));
let fixtures_dir: PathBuf = if let Some(p) = std::env::var_os("CORTEX_FIXTURES_DIR") {
PathBuf::from(p)
} else {
layout.data_dir.join("replay")
};
let event_log = layout.event_log_path.clone();
let _available_gates: &[GateId] = &[];
let mut registry = ToolRegistry::new();
registry.register(Box::new(CortexSearchTool {
pool: Arc::clone(&pool),
}));
registry.register(Box::new(CortexContextTool {
pool: Arc::clone(&pool),
}));
registry.register(Box::new(CortexMemoryHealthTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexConfigTool::new()));
registry.register(Box::new(CortexSuggestTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexMemoryListTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexMemoryOutcomeTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexDecayStatusTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexDoctorTool::new(
Arc::clone(&pool),
event_log.clone(),
)));
registry.register(Box::new(CortexAuditVerifyTool::new(
Arc::clone(&pool),
event_log.clone(),
)));
registry.register(Box::new(CortexReflectTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexModelsListTool::new()));
registry.register(Box::new(CortexMemoryEmbedTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexSessionCloseTool::new(
Arc::clone(&pool),
event_log.clone(),
fixtures_dir,
)));
registry.register(Box::new(CortexMemoryAcceptTool::new(
Arc::clone(&pool),
Arc::clone(&session_token),
auto_commit_enabled,
)));
registry.register(Box::new(CortexAdmitAxiomTool::new(Arc::clone(&pool))));
registry.register(Box::new(CortexSessionCommitTool::new(
Arc::clone(&pool),
Arc::clone(&session_token),
auto_commit_enabled,
)));
tracing::info!(
tools = "cortex_search,cortex_context,cortex_memory_health,cortex_config,\
cortex_suggest,cortex_memory_list,cortex_memory_outcome,\
cortex_decay_status,cortex_doctor,cortex_audit_verify,cortex_reflect,\
cortex_models_list,cortex_memory_embed,cortex_session_close,\
cortex_memory_accept,cortex_admit_axiom,cortex_session_commit",
"cortex serve: starting stdio MCP server"
);
match cortex_mcp::serve::run_stdio_server(registry) {
Ok(()) => {
tracing::info!("cortex serve: stdio loop exited cleanly (EOF)");
Exit::Ok
}
Err(err) => {
eprintln!("cortex serve: stdio server error: {err}");
Exit::Internal
}
}
}
fn confirmation_token() -> String {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const TOKEN_LEN: usize = 6;
let seed = {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos();
let pid = std::process::id();
(nanos as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15)
^ (pid as u64).wrapping_mul(0x6c62_272e_07bb_0142)
};
let mut state = seed;
let mut chars = [0u8; TOKEN_LEN];
for ch in &mut chars {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*ch = ALPHABET[(state as usize) % ALPHABET.len()];
}
String::from_utf8(chars.to_vec()).expect("ALPHABET contains only ASCII")
}