use clap::{Args, Subcommand};
use crate::cli::args::BackendArgs;
use crate::cli::output::OutputFormat;
use crate::error::ProxyResult;
use crate::proxy::BackendConnector;
use crate::proxy::BackendTransport;
use crate::proxy::backend::BackendConfig;
#[derive(Debug, Args)]
pub struct AdapterCommand {
#[command(flatten)]
pub backend: BackendArgs,
#[command(subcommand)]
pub protocol: AdapterProtocol,
#[arg(long, value_name = "ADDR", default_value = "127.0.0.1:3001")]
pub bind: String,
#[arg(long, default_value = "turbomcp-proxy")]
pub client_name: String,
#[arg(long, default_value = env!("CARGO_PKG_VERSION"))]
pub client_version: String,
}
#[derive(Debug, Subcommand)]
pub enum AdapterProtocol {
Rest {
#[arg(long)]
openapi_ui: bool,
},
#[command(name = "graphql", visible_alias = "gql")]
GraphQL {
#[arg(long)]
playground: bool,
},
}
impl AdapterCommand {
pub async fn execute(self, _format: OutputFormat) -> ProxyResult<()> {
self.backend
.validate()
.map_err(crate::error::ProxyError::configuration)?;
let backend_config = self.create_backend_config()?;
tracing::info!("Connecting to backend...");
let backend = BackendConnector::new(backend_config).await?;
tracing::info!("Backend connected successfully");
tracing::info!("Introspecting backend capabilities...");
let spec = backend.introspect().await?;
tracing::info!(
"Backend introspection complete: {} tools, {} resources, {} prompts",
spec.tools.len(),
spec.resources.len(),
spec.prompts.len()
);
match &self.protocol {
AdapterProtocol::Rest { openapi_ui } => self.start_rest_adapter(*openapi_ui)?,
AdapterProtocol::GraphQL { playground } => self.start_graphql_adapter(*playground)?,
}
Ok(())
}
fn start_rest_adapter(&self, enable_openapi_ui: bool) -> ProxyResult<()> {
tracing::info!("Starting REST API adapter on {}", self.bind);
if enable_openapi_ui {
tracing::info!(" OpenAPI UI: http://{}/docs", self.bind);
}
tracing::info!(" API base: http://{}/api", self.bind);
Err(crate::error::ProxyError::configuration(
"REST adapter not yet fully implemented",
))
}
fn start_graphql_adapter(&self, enable_playground: bool) -> ProxyResult<()> {
tracing::info!("Starting GraphQL adapter on {}", self.bind);
if enable_playground {
tracing::info!(" GraphQL Playground: http://{}/playground", self.bind);
}
tracing::info!(" GraphQL endpoint: http://{}/graphql", self.bind);
Err(crate::error::ProxyError::configuration(
"GraphQL adapter not yet fully implemented",
))
}
fn create_backend_config(&self) -> ProxyResult<BackendConfig> {
use crate::cli::args::BackendType;
let transport = match self.backend.backend_type() {
Some(BackendType::Stdio) => {
let cmd = self.backend.cmd.as_ref().ok_or_else(|| {
crate::error::ProxyError::configuration("Command not specified".to_string())
})?;
BackendTransport::Stdio {
command: cmd.clone(),
args: self.backend.args.clone(),
working_dir: self
.backend
.working_dir
.as_ref()
.map(|p| p.to_string_lossy().to_string()),
}
}
Some(BackendType::Http) => {
let url = self.backend.http.as_ref().ok_or_else(|| {
crate::error::ProxyError::configuration("HTTP URL not specified".to_string())
})?;
BackendTransport::Http {
url: url.clone(),
auth_token: None,
}
}
Some(BackendType::Tcp) => {
let addr = self.backend.tcp.as_ref().ok_or_else(|| {
crate::error::ProxyError::configuration("TCP address not specified".to_string())
})?;
let parts: Vec<&str> = addr.split(':').collect();
if parts.len() != 2 {
return Err(crate::error::ProxyError::configuration(
"Invalid TCP address format. Use host:port".to_string(),
));
}
let host = parts[0].to_string();
let port = parts[1].parse::<u16>().map_err(|_| {
crate::error::ProxyError::configuration("Invalid port number".to_string())
})?;
BackendTransport::Tcp { host, port }
}
#[cfg(unix)]
Some(BackendType::Unix) => {
let path = self.backend.unix.as_ref().ok_or_else(|| {
crate::error::ProxyError::configuration(
"Unix socket path not specified".to_string(),
)
})?;
BackendTransport::Unix { path: path.clone() }
}
Some(BackendType::Websocket) => {
let url = self.backend.websocket.as_ref().ok_or_else(|| {
crate::error::ProxyError::configuration(
"WebSocket URL not specified".to_string(),
)
})?;
BackendTransport::WebSocket { url: url.clone() }
}
None => {
return Err(crate::error::ProxyError::configuration(
"No backend specified".to_string(),
));
}
};
Ok(BackendConfig {
transport,
client_name: self.client_name.clone(),
client_version: self.client_version.clone(),
})
}
}