use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
pub mod r#gen;
pub mod ir;
pub mod spec;
#[derive(Parser)]
#[command(name = "mcp-forge", version, about = "Generate Rust MCP servers from OpenAPI specs")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Generate {
#[arg(long, short)]
spec: PathBuf,
#[arg(long, short, default_value = ".")]
output: PathBuf,
#[arg(long)]
name: Option<String>,
},
Inspect {
#[arg(long, short)]
spec: PathBuf,
},
}
fn load_spec(path: &Path) -> Result<ir::ApiSpec> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("failed to read spec: {}", path.display()))?;
let openapi: spec::OpenApiSpec = if path.extension().is_some_and(|e| e == "json") {
serde_json::from_str(&content)?
} else {
serde_yaml_ng::from_str(&content)?
};
Ok(ir::ApiSpec::from_openapi(&openapi))
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
let cli = Cli::parse();
match cli.command {
Command::Generate { spec, output, name } => {
let mut api = load_spec(&spec)?;
if let Some(n) = name {
api.name = n;
}
r#gen::generate(&api, &output).context("failed to generate project")?;
tracing::info!(
"Generated MCP server '{}' ({} operations, {} types) → {}",
api.name,
api.operations.len(),
api.types.len(),
output.display()
);
}
Command::Inspect { spec } => {
let api = load_spec(&spec)?;
println!("Name: {}", api.name);
println!("Version: {}", api.version);
println!("Base URL: {}", api.base_url.as_deref().unwrap_or("-"));
println!("Auth: {}", api.auth);
println!("\nOperations ({}):", api.operations.len());
for op in &api.operations {
println!(
" {} {} → {} ({})",
op.method,
op.path,
op.id,
op.summary.as_deref().unwrap_or("-")
);
}
println!("\nTypes ({}):", api.types.len());
for t in &api.types {
if t.is_enum {
println!(" enum {} ({} variants)", t.rust_name, t.enum_variants.len());
} else {
println!(" struct {} ({} fields)", t.rust_name, t.fields.len());
}
}
}
}
Ok(())
}