use clap::Parser;
use std::env;
use std::sync::Arc;
use tap_agent::{Agent, TapAgent};
use tracing::{error, info};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod error;
mod mcp;
mod resources;
mod tap_integration;
mod tools;
use error::Result;
#[derive(Parser)]
#[command(
name = "tap-mcp",
about = "Model Context Protocol server for TAP Node functionality",
version = env!("CARGO_PKG_VERSION")
)]
struct Args {
#[arg(long, short)]
debug: bool,
#[arg(long)]
agent_did: Option<String>,
#[arg(long)]
tap_root: Option<String>,
#[arg(long, env = "TAP_SECRET_HELPER")]
secret_helper: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let mut args = Args::parse();
if args.tap_root.is_none() {
args.tap_root = env::var("TAP_ROOT")
.ok()
.or_else(|| env::var("TAP_HOME").ok());
}
if let Some(ref tap_root) = args.tap_root {
env::set_var("TAP_HOME", tap_root);
}
let level = if args.debug { "debug" } else { "info" };
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| format!("tap_mcp={},tap_node=info", level).into()),
)
.with(
tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_ansi(true),
)
.init();
info!("Starting TAP-MCP server v{}", env!("CARGO_PKG_VERSION"));
let (agent, agent_did) = if let Some(ref helper_cmd) = args.secret_helper {
use tap_agent::secret_helper::{self, SecretHelperConfig};
let config = SecretHelperConfig::from_command_string(helper_cmd)?;
if let Some(did) = args.agent_did {
info!("Using secret helper for DID: {}", did);
let (agent, did) = TapAgent::from_secret_helper(&config, &did, args.debug).await?;
(Arc::new(agent), did)
} else {
let tap_root = args.tap_root.as_ref().map(std::path::PathBuf::from);
let dids = secret_helper::discover_agent_dids(tap_root.as_deref())?;
if let Some(did) = dids.first() {
info!(
"Discovered DID {} from tap directory, using secret helper",
did
);
let (agent, did) = TapAgent::from_secret_helper(&config, did, args.debug).await?;
(Arc::new(agent), did)
} else {
return Err(tap_agent::Error::Storage(
"No agent DIDs found in tap directory for secret helper".to_string(),
)
.into());
}
}
} else if let Some(did) = args.agent_did {
info!("Using provided agent DID: {}", did);
match TapAgent::from_stored_keys(Some(did.clone()), true).await {
Ok(agent) => (Arc::new(agent), did),
Err(e) => {
error!("Failed to load agent with DID {}: {}", did, e);
return Err(e.into());
}
}
} else {
match TapAgent::from_stored_keys(None, true).await {
Ok(agent) => {
let did = agent.get_agent_did().to_string();
info!("Loaded agent from stored keys with DID: {}", did);
(Arc::new(agent), did)
}
Err(e) => {
info!("No stored keys found ({}), creating new agent...", e);
use tap_agent::agent_key_manager::AgentKeyManagerBuilder;
use tap_agent::config::AgentConfig;
use tap_agent::did::{DIDGenerationOptions, KeyType};
use tap_agent::key_manager::KeyManager;
use tap_agent::storage::KeyStorage;
let default_key_path = KeyStorage::default_key_path().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not determine default key path",
)
})?;
let key_manager_builder =
AgentKeyManagerBuilder::new().load_from_path(default_key_path);
let key_manager = key_manager_builder.build()?;
let generated_key = key_manager.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})?;
info!("Generated new agent with DID: {}", generated_key.did);
let config = AgentConfig::new(generated_key.did.clone()).with_debug(true);
let agent = TapAgent::new(config, Arc::new(key_manager));
info!("New key saved to storage successfully");
(Arc::new(agent), generated_key.did)
}
}
};
let tap_integration = tap_integration::TapIntegration::new(
Some(&agent_did),
args.tap_root.as_deref(),
Some(agent.clone()),
)
.await?;
info!(
"TAP integration initialized using TapNode with DID-based storage at ~/.tap/{}",
agent_did.replace(':', "_")
);
let mcp_server = mcp::McpServer::new(tap_integration).await?;
info!("Starting MCP server on stdio");
if let Err(e) = mcp_server.run().await {
error!("MCP server error: {}", e);
return Err(e);
}
Ok(())
}