#![deny(clippy::unwrap_used)]
mod auth;
mod cli;
mod filter;
mod headers;
mod logging;
mod proxy;
mod session;
mod transport;
use anyhow::{Context, Result};
use clap::Parser;
use rmcp::service::{RoleServer, serve_directly_with_ct};
use rmcp::transport::io::stdio;
use tokio::signal;
use tokio_util::sync::CancellationToken;
use crate::cli::Cli;
use crate::filter::ToolFilter;
use crate::proxy::{KeepaliveConfig, ProxyHandler};
use crate::session::{CredentialKey, SessionHash};
#[tokio::main]
async fn main() -> Result<()> {
let _ = &logging::install_panic_hook;
let cli = Cli::parse();
cli.validate().context("invalid CLI arguments")?;
let headers = headers::parse(&cli.headers).context("failed to parse --header arguments")?;
let session = SessionHash::new(&cli.server_url, cli.resource.as_deref(), &headers);
let cred_key = CredentialKey::new(&cli.server_url, cli.resource.as_deref());
tracing::info!(
server_url = %cli.server_url,
%session,
%cred_key,
header_count = headers.len(),
"starting hyper-mcp-remote"
);
let auth_outcome = auth::acquire_auth_client(&cli, &cred_key, &headers)
.await
.context("failed to authenticate with remote MCP server")?;
let transport = transport::build(&cli.server_url, headers, auth_outcome)
.context("failed to build remote transport")?;
let keepalive = KeepaliveConfig::from_secs(cli.ping_interval_secs, cli.ping_timeout_secs);
let tool_filter = ToolFilter::from_cli(&cli.allow_tools, &cli.deny_tools)
.context("failed to compile tool filter patterns")?;
tracing::info!(
ping_interval_secs = cli.ping_interval_secs,
ping_timeout_secs = cli.ping_timeout_secs,
allow_tool_patterns = cli.allow_tools.len(),
deny_tool_patterns = cli.deny_tools.len(),
tool_filter_active = !tool_filter.is_noop(),
"Starting hyper-mcp-remote"
);
let handler = ProxyHandler::new(transport, keepalive, tool_filter);
let ct = CancellationToken::new();
let running =
serve_directly_with_ct::<RoleServer, _, _, _, _>(handler, stdio(), None, ct.clone());
tokio::select! {
res = running.waiting() => {
tracing::warn!(reason = ?res?, "Shutting down");
}
_ = signal::ctrl_c() => {
tracing::warn!(reason = "SIGTERM", "Shutting down");
ct.cancel();
}
}
Ok(())
}