use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use layover_core::agent::AgentName;
use layover_core::flight::{ItineraryId, RunId};
use layover_mcp::Session;
use serde_json::json;
pub const TOKEN_VAR: &str = "LAYOVER_RUN_TOKEN";
pub const ENDPOINT_VAR: &str = "LAYOVER_MCP_URL";
#[derive(Debug, Default)]
pub struct Tokens {
live: Mutex<HashMap<String, Session>>,
}
impl Tokens {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn mint(
&self,
run: RunId,
agent: AgentName,
itinerary: ItineraryId,
hops_remaining: u32,
) -> String {
let token = format!("lvt_{}", RunId::generate().as_str().replace("run_", ""));
let session = Session {
run,
agent,
itinerary,
hops_remaining,
};
if let Ok(mut live) = self.live.lock() {
live.insert(token.clone(), session);
}
token
}
#[must_use]
pub fn resolve(&self, token: &str) -> Option<Session> {
self.live.lock().ok()?.get(token).cloned()
}
pub fn revoke(&self, token: &str) {
if let Ok(mut live) = self.live.lock() {
live.remove(token);
}
}
#[must_use]
pub fn live_count(&self) -> usize {
self.live.lock().map_or(0, |live| live.len())
}
}
pub fn write_config(
hangar: &Path,
format: &str,
endpoint: &str,
token: &str,
) -> std::io::Result<PathBuf> {
let path = hangar.join("mcp.json");
let body = match format {
"codex_toml" => format!(
"[mcp_servers.layover]\nurl = \"{endpoint}\"\nheaders = {{ Authorization = \"Bearer {token}\" }}\n"
),
_ => serde_json::to_string_pretty(&json!({
"mcpServers": {
"layover": {
"url": endpoint,
"headers": { "Authorization": format!("Bearer {token}") },
}
}
}))
.unwrap_or_default(),
};
std::fs::write(&path, body)?;
Ok(path)
}
#[cfg(test)]
mod tests {
use super::*;
fn tokens() -> Tokens {
Tokens::new()
}
fn mint(registry: &Tokens, agent: &str) -> String {
registry.mint(
RunId::generate(),
AgentName::new(agent),
ItineraryId::generate(),
3,
)
}
#[test]
fn a_token_resolves_to_the_run_that_holds_it() {
let registry = tokens();
let token = mint(®istry, "analyst");
let session = registry.resolve(&token).expect("live");
assert_eq!(session.agent, AgentName::new("analyst"));
assert_eq!(session.hops_remaining, 3);
}
#[test]
fn a_token_is_not_derivable_from_the_agent_it_belongs_to() {
let registry = tokens();
let first = mint(®istry, "analyst");
let second = mint(®istry, "analyst");
assert_ne!(
first, second,
"two runs of one agent must not share a token"
);
assert!(!first.contains("analyst"));
}
#[test]
fn an_unknown_token_resolves_to_nobody() {
assert!(tokens().resolve("lvt_invented").is_none());
}
#[test]
fn a_revoked_token_stops_working_immediately() {
let registry = tokens();
let token = mint(®istry, "analyst");
registry.revoke(&token);
assert!(registry.resolve(&token).is_none());
assert_eq!(registry.live_count(), 0);
}
#[test]
fn revoking_one_run_does_not_disturb_another() {
let registry = tokens();
let first = mint(®istry, "analyst");
let second = mint(®istry, "developer");
registry.revoke(&first);
assert!(registry.resolve(&second).is_some());
}
#[test]
fn the_written_config_carries_the_endpoint_and_the_token() {
let dir = std::env::temp_dir().join(format!("layover-mcpcfg-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("temp dir");
let path = write_config(&dir, "claude_json", "http://127.0.0.1:7878/mcp", "lvt_abc")
.expect("writes");
let text = std::fs::read_to_string(&path).expect("reads");
assert!(text.contains("http://127.0.0.1:7878/mcp"), "{text}");
assert!(text.contains("Bearer lvt_abc"), "{text}");
assert!(text.contains("layover"), "{text}");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn an_unknown_dialect_falls_back_rather_than_refusing() {
let dir = std::env::temp_dir().join(format!("layover-mcpcfg2-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("temp dir");
let path = write_config(&dir, "something_new", "http://x/mcp", "lvt_1").expect("writes");
let text = std::fs::read_to_string(&path).expect("reads");
assert!(text.contains("mcpServers"), "{text}");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn codex_gets_the_dialect_it_asked_for() {
let dir = std::env::temp_dir().join(format!("layover-mcpcfg3-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("temp dir");
let path = write_config(&dir, "codex_toml", "http://x/mcp", "lvt_1").expect("writes");
let text = std::fs::read_to_string(&path).expect("reads");
assert!(text.contains("[mcp_servers.layover]"), "{text}");
let _ = std::fs::remove_dir_all(&dir);
}
}