use anyhow::Result;
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
pub struct McpArgs {
#[command(subcommand)]
pub command: McpCommands,
}
#[derive(Subcommand, Debug)]
pub enum McpCommands {
Serve(ServeArgs),
}
#[derive(Args, Debug, Default)]
pub struct ServeArgs {
#[arg(long)]
pub workspace_root: Option<PathBuf>,
}
pub async fn execute(args: McpArgs) -> Result<()> {
match args.command {
McpCommands::Serve(args) => {
let mut config = ralph_api::ApiConfig::from_env()?;
config.served_by = "ralph-mcp".to_string();
config.auth_mode = ralph_api::AuthMode::TrustedLocal;
config.token = None;
if let Some(workspace_root) = resolve_workspace_root(args.workspace_root)? {
config.workspace_root = workspace_root;
}
ralph_api::serve_stdio(config).await
}
}
}
fn resolve_workspace_root(workspace_root: Option<PathBuf>) -> Result<Option<PathBuf>> {
let Some(workspace_root) = workspace_root else {
return Ok(None);
};
let resolved = if workspace_root.is_absolute() {
workspace_root
} else {
std::env::current_dir()?.join(workspace_root)
};
Ok(Some(resolved))
}
#[cfg(test)]
mod tests {
use super::resolve_workspace_root;
use crate::test_support::CwdGuard;
use anyhow::Result;
use std::path::PathBuf;
#[test]
fn resolve_workspace_root_returns_none_when_unset() -> Result<()> {
assert_eq!(resolve_workspace_root(None)?, None);
Ok(())
}
#[test]
fn resolve_workspace_root_preserves_absolute_paths() -> Result<()> {
let path = PathBuf::from("/tmp/ralph-workspace");
assert_eq!(resolve_workspace_root(Some(path.clone()))?, Some(path));
Ok(())
}
#[test]
fn resolve_workspace_root_resolves_relative_paths_from_current_dir() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let canonical = temp_dir.path().canonicalize()?;
let _guard = CwdGuard::set(temp_dir.path());
let resolved = resolve_workspace_root(Some(PathBuf::from("nested/workspace")))?;
assert_eq!(resolved, Some(canonical.join("nested/workspace")));
Ok(())
}
}