use clap::Parser;
use database_mcp_config::DatabaseConfig;
use rmcp::ServiceExt;
use tracing::{error, info};
use crate::commands::common::{self, DatabaseArguments};
use crate::error::Error;
#[derive(Debug, Parser)]
pub(crate) struct StdioCommand {
#[command(flatten)]
db_arguments: DatabaseArguments,
}
impl StdioCommand {
pub(crate) async fn execute(&self) -> Result<(), Error> {
let db_config = DatabaseConfig::try_from(&self.db_arguments)?;
let server = common::create_server(&db_config);
info!("Starting MCP server via stdio transport...");
let transport = rmcp::transport::io::stdio();
let running = server.serve(transport).await?;
if let Err(join_error) = running.waiting().await {
error!("stdio server task terminated abnormally: {join_error}");
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use database_mcp_config::{ConfigError, DatabaseBackend};
#[track_caller]
fn parse(args: &[&str]) -> StdioCommand {
StdioCommand::try_parse_from(args).expect("valid stdio command")
}
#[test]
fn db_read_only_defaults_to_true() {
let cmd = parse(&["_"]);
assert!(cmd.db_arguments.read_only);
}
#[test]
fn db_query_timeout_zero_passes_through() {
let cmd = parse(&["_", "--db-query-timeout", "0"]);
let config = DatabaseConfig::try_from(&cmd.db_arguments).expect("valid db args");
assert_eq!(config.query_timeout, Some(0));
}
#[test]
fn db_args_populate_database_config() {
let cmd = parse(&["_", "--db-backend", "postgres", "--db-user", "pg", "--db-name", "app"]);
assert_eq!(cmd.db_arguments.backend, DatabaseBackend::Postgres);
assert_eq!(cmd.db_arguments.user.as_deref(), Some("pg"));
assert_eq!(cmd.db_arguments.name.as_deref(), Some("app"));
let config = DatabaseConfig::try_from(&cmd.db_arguments).expect("valid postgres args");
assert_eq!(config.backend, DatabaseBackend::Postgres);
assert_eq!(config.user, "pg");
assert_eq!(config.name.as_deref(), Some("app"));
}
#[test]
fn try_from_database_arguments_propagates_validation_errors() {
let cmd = parse(&["_", "--db-backend", "sqlite"]);
let errors =
DatabaseConfig::try_from(&cmd.db_arguments).expect_err("sqlite without --db-name must be rejected");
assert!(errors.iter().any(|e| matches!(e, ConfigError::MissingSqliteDbName)));
}
}