mod mcp;
mod relay;
use anyhow::Result;
use clap::Parser;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use mcp::McpServer;
use relay::RelayClient;
const DEFAULT_RELAY_URL: &str = "http://localhost:8080";
#[derive(Parser, Debug)]
#[command(name = "scemadex-mcp", about = "MCP server for the ScemaDEX agentic-liquidity rail")]
struct Cli {
#[arg(long)]
relay_url: Option<String>,
#[cfg(feature = "pay")]
#[arg(long)]
keypair: Option<String>,
}
fn resolve_relay_url(flag: Option<String>) -> String {
flag.or_else(|| std::env::var("SCEMADEX_RELAY_URL").ok())
.unwrap_or_else(|| DEFAULT_RELAY_URL.to_string())
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_target(false)
.init();
let cli = Cli::parse();
let relay_url = resolve_relay_url(cli.relay_url.clone());
#[allow(unused_mut)]
let mut relay = RelayClient::new(relay_url);
#[cfg(feature = "pay")]
if let Some(kp_path) = &cli.keypair {
let kp = solana_sdk::signature::read_keypair_file(kp_path)
.map_err(|e| anyhow::anyhow!("read keypair {kp_path}: {e}"))?;
relay = relay.with_payer(std::sync::Arc::new(kp));
tracing::info!("x402 auto-pay enabled (payer loaded from {kp_path})");
}
match relay.health().await {
Ok(r) if r.status == 200 => tracing::info!("relay healthy at {}", relay.base_url()),
Ok(r) => tracing::warn!("relay at {} returned {} on /health", relay.base_url(), r.status),
Err(e) => tracing::warn!("relay at {} not reachable yet: {e}", relay.base_url()),
}
let server = McpServer::new(relay);
tracing::info!("scemadex-mcp ready");
let stdin = tokio::io::stdin();
let mut reader = BufReader::new(stdin).lines();
let mut stdout = tokio::io::stdout();
while let Some(line) = reader.next_line().await? {
if let Some(response) = server.handle_line(&line).await {
stdout.write_all(response.as_bytes()).await?;
stdout.write_all(b"\n").await?;
stdout.flush().await?;
}
}
Ok(())
}