#![forbid(unsafe_code)]
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::process::ExitCode;
use std::sync::Arc;
use anyhow::{Context, Result};
use rmcp::ServiceExt;
use rmcp::transport::io::stdio;
use tokio_util::sync::CancellationToken;
use deribit_mcp::config::{Config, Transport};
use deribit_mcp::context::AdapterContext;
use deribit_mcp::http_transport;
use deribit_mcp::observability;
use deribit_mcp::server::DeribitMcpServer;
const EXIT_CONFIG_ERROR: u8 = 1;
#[tokio::main]
async fn main() -> ExitCode {
match run().await {
Ok(()) => ExitCode::from(0),
Err(err) => {
let mut message = err.to_string();
for cause in err.chain().skip(1) {
message.push_str(": ");
message.push_str(&cause.to_string());
}
eprintln!("deribit-mcp: startup error: {message}");
ExitCode::from(EXIT_CONFIG_ERROR)
}
}
}
async fn run() -> Result<()> {
let config = Config::load().context("loading configuration")?;
observability::init(&config);
let endpoint = config.endpoint.clone();
let env_label = if endpoint.contains("test.deribit.com") {
"TESTNET"
} else {
"MAINNET"
};
let ctx = Arc::new(
AdapterContext::new(Arc::new(config.clone())).context("building adapter context")?,
);
let server = DeribitMcpServer::new(ctx.clone());
let shutdown = CancellationToken::new();
install_signal_handlers(shutdown.clone());
match config.transport {
Transport::Stdio => {
tracing::info!(
target: "deribit_mcp::startup",
env = env_label,
endpoint = %endpoint,
transport = "stdio",
"starting on {env_label} ({endpoint}); transport=stdio"
);
let running = server
.serve_with_ct(stdio(), shutdown.clone())
.await
.context("starting stdio transport")?;
let reason = running.waiting().await.context("stdio service exited")?;
tracing::info!(?reason, "stdio service stopped");
}
Transport::Http => {
let listen = config.http_listen;
let bearer_status = if config.http_bearer_token.is_some() {
"set"
} else {
"none"
};
tracing::info!(
target: "deribit_mcp::startup",
env = env_label,
endpoint = %endpoint,
transport = "http",
listen = %listen,
bearer = bearer_status,
"starting on {env_label} ({endpoint}); transport=http; listen={listen}; bearer={bearer_status}"
);
let cfg = Arc::new(config);
http_transport::serve(cfg, ctx.clone(), shutdown)
.await
.context("HTTP transport")?;
}
}
#[cfg(feature = "fix")]
if let Err(err) = ctx.shutdown_fix().await {
tracing::warn!(error = %err, "fix shutdown failed");
}
Ok(())
}
fn install_signal_handlers(shutdown: CancellationToken) {
tokio::spawn(async move {
let signal = first_termination_signal().await;
tracing::info!(?signal, "shutdown signal received; cancelling");
shutdown.cancel();
});
}
async fn first_termination_signal() -> &'static str {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let mut sigterm = match signal(SignalKind::terminate()) {
Ok(s) => s,
Err(err) => {
tracing::warn!(error = %err, "failed to install SIGTERM handler; SIGINT only");
wait_for_ctrl_c().await;
return "SIGINT";
}
};
tokio::select! {
_ = wait_for_ctrl_c() => "SIGINT",
_ = sigterm.recv() => "SIGTERM",
}
}
#[cfg(not(unix))]
{
wait_for_ctrl_c().await;
"SIGINT"
}
}
async fn wait_for_ctrl_c() {
match tokio::signal::ctrl_c().await {
Ok(()) => {}
Err(err) => {
tracing::warn!(
error = %err,
"failed to install SIGINT handler; relying on SIGTERM / orchestrator"
);
std::future::pending::<()>().await;
}
}
}