use std::fs;
use std::io;
use std::path::Path;
mod templates;
use crate::VERSION;
#[derive(Debug, thiserror::Error)]
pub enum GeneratorError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Template error: {0}")]
Template(String),
#[error("Invalid name: {0}")]
InvalidName(String),
}
pub fn generate_server(name: &str, output_dir: &Path) -> Result<(), GeneratorError> {
if !is_valid_name(name) {
return Err(GeneratorError::InvalidName(format!(
"Invalid server name: {}",
name
)));
}
let server_dir = output_dir.join(name);
fs::create_dir_all(&server_dir)?;
generate_server_main(&server_dir, name)?;
generate_server_cargo_toml(&server_dir, name)?;
generate_server_readme(&server_dir, name)?;
println!(
"Server stub '{}' generated successfully in '{}'",
name,
server_dir.display()
);
println!("To run the server:");
println!(" cd {}", server_dir.display());
println!(" cargo run");
Ok(())
}
pub fn generate_client(name: &str, output_dir: &Path) -> Result<(), GeneratorError> {
if !is_valid_name(name) {
return Err(GeneratorError::InvalidName(format!(
"Invalid client name: {}",
name
)));
}
let client_dir = output_dir.join(name);
fs::create_dir_all(&client_dir)?;
generate_client_main(&client_dir, name)?;
generate_client_cargo_toml(&client_dir, name)?;
generate_client_readme(&client_dir, name)?;
println!(
"Client stub '{}' generated successfully in '{}'",
name,
client_dir.display()
);
println!("To run the client:");
println!(" cd {}", client_dir.display());
println!(" cargo run -- --uri <server_uri>");
Ok(())
}
pub fn generate_project(
name: &str,
output_dir: &str,
transport_type: &str,
) -> Result<(), GeneratorError> {
println!(
"Generating complete 'hello mcp' project '{}' in '{}'",
name, output_dir
);
println!("Using transport type: {}", transport_type);
let project_dir = Path::new(output_dir).join(name);
fs::create_dir_all(&project_dir)?;
let server_dir = project_dir.join("server");
fs::create_dir_all(&server_dir)?;
let client_dir = project_dir.join("client");
fs::create_dir_all(&client_dir)?;
generate_project_server(&server_dir, name, transport_type)?;
generate_project_client(&client_dir, name, transport_type)?;
generate_project_readme(&project_dir, name, transport_type)?;
generate_project_test_script(&project_dir, name, transport_type)?;
println!(
"Project '{}' with {} transport generated successfully in '{}'",
name,
transport_type,
project_dir.display()
);
println!("To run the server:");
println!(" cd {}/{}/server", output_dir, name);
println!(" cargo run");
println!("To run the client:");
println!(" cd {}/{}/client", output_dir, name);
println!(" cargo run -- --interactive");
println!("To run the tests:");
println!(" cd {}/{}", output_dir, name);
println!(" ./test.sh");
println!("Complete 'hello mcp' project generated successfully!");
Ok(())
}
fn is_valid_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
if !name.chars().next().unwrap().is_ascii_alphabetic() {
return false;
}
name.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}
fn generate_server_main(server_dir: &Path, name: &str) -> Result<(), GeneratorError> {
let main_rs = server_dir.join("src").join("main.rs");
fs::create_dir_all(main_rs.parent().unwrap())?;
let content = templates::SERVER_MAIN_TEMPLATE.replace("{{name}}", name);
fs::write(main_rs, content)?;
Ok(())
}
fn generate_server_cargo_toml(server_dir: &Path, name: &str) -> Result<(), GeneratorError> {
let cargo_toml = server_dir.join("Cargo.toml");
let content = templates::SERVER_CARGO_TEMPLATE
.replace("{{name}}", name)
.replace("{{version}}", VERSION);
fs::write(cargo_toml, content)?;
Ok(())
}
fn generate_server_readme(server_dir: &Path, name: &str) -> Result<(), GeneratorError> {
let readme = server_dir.join("README.md");
let content = templates::SERVER_README_TEMPLATE.replace("{{name}}", name);
fs::write(readme, content)?;
Ok(())
}
fn generate_client_main(client_dir: &Path, name: &str) -> Result<(), GeneratorError> {
let main_rs = client_dir.join("src").join("main.rs");
fs::create_dir_all(main_rs.parent().unwrap())?;
let content = templates::CLIENT_MAIN_TEMPLATE.replace("{{name}}", name);
fs::write(main_rs, content)?;
Ok(())
}
fn generate_client_cargo_toml(client_dir: &Path, name: &str) -> Result<(), GeneratorError> {
let cargo_toml = client_dir.join("Cargo.toml");
let content = templates::CLIENT_CARGO_TEMPLATE
.replace("{{name}}", name)
.replace("{{version}}", VERSION);
fs::write(cargo_toml, content)?;
Ok(())
}
fn generate_client_readme(client_dir: &Path, name: &str) -> Result<(), GeneratorError> {
let readme = client_dir.join("README.md");
let content = templates::CLIENT_README_TEMPLATE.replace("{{name}}", name);
fs::write(readme, content)?;
Ok(())
}
fn generate_project_server(
server_dir: &Path,
name: &str,
transport_type: &str,
) -> Result<(), GeneratorError> {
let src_dir = server_dir.join("src");
fs::create_dir_all(&src_dir)?;
let main_rs = src_dir.join("main.rs");
let template = match transport_type {
"stdio" => templates::STDIO_SERVER_TEMPLATE,
"sse" => templates::SSE_SERVER_TEMPLATE,
_ => {
return Err(GeneratorError::Template(format!(
"Unsupported transport type: {}. Supported types are 'stdio' and 'sse'. WebSocket transport is planned but not yet implemented.",
transport_type
)))
}
};
let content = template.replace("{{name}}", name);
fs::write(main_rs, content)?;
let cargo_toml = server_dir.join("Cargo.toml");
let cargo_template = match transport_type {
"stdio" => templates::STDIO_SERVER_CARGO_TEMPLATE,
"sse" => templates::SSE_SERVER_CARGO_TEMPLATE,
_ => {
return Err(GeneratorError::Template(format!(
"Unsupported transport type: {}. Supported types are 'stdio' and 'sse'. WebSocket transport is planned but not yet implemented.",
transport_type
)))
}
};
let content = cargo_template
.replace("{{name}}", name)
.replace("{{version}}", VERSION);
fs::write(cargo_toml, content)?;
Ok(())
}
fn generate_project_client(
client_dir: &Path,
name: &str,
transport_type: &str,
) -> Result<(), GeneratorError> {
let src_dir = client_dir.join("src");
fs::create_dir_all(&src_dir)?;
let main_rs = src_dir.join("main.rs");
let template = match transport_type {
"stdio" => templates::STDIO_CLIENT_TEMPLATE,
"sse" => templates::SSE_CLIENT_TEMPLATE,
_ => {
return Err(GeneratorError::Template(format!(
"Unsupported transport type: {}. Supported types are 'stdio' and 'sse'. WebSocket transport is planned but not yet implemented.",
transport_type
)))
}
};
let content = template.replace("{{name}}", name);
fs::write(main_rs, content)?;
let cargo_toml = client_dir.join("Cargo.toml");
let cargo_template = match transport_type {
"stdio" => templates::STDIO_CLIENT_CARGO_TEMPLATE,
"sse" => templates::SSE_CLIENT_CARGO_TEMPLATE,
_ => {
return Err(GeneratorError::Template(format!(
"Unsupported transport type: {}. Supported types are 'stdio' and 'sse'. WebSocket transport is planned but not yet implemented.",
transport_type
)))
}
};
let content = cargo_template
.replace("{{name}}", name)
.replace("{{version}}", VERSION);
fs::write(cargo_toml, content)?;
Ok(())
}
fn generate_project_readme(
project_dir: &Path,
name: &str,
transport_type: &str,
) -> Result<(), GeneratorError> {
let readme = project_dir.join("README.md");
let readme_template = match transport_type {
"stdio" => templates::PROJECT_README_STDIO_TEMPLATE,
"sse" => templates::PROJECT_README_SSE_TEMPLATE,
_ => {
return Err(GeneratorError::Template(format!(
"Unsupported transport type: {}. Supported types are 'stdio' and 'sse'. WebSocket transport is planned but not yet implemented.",
transport_type
)))
}
};
let content = readme_template.replace("{{name}}", name);
fs::write(readme, content)?;
Ok(())
}
fn generate_project_test_script(
project_dir: &Path,
name: &str,
transport_type: &str,
) -> Result<(), GeneratorError> {
let test_script = project_dir.join("test.sh");
let test_script_template = match transport_type {
"stdio" => templates::STDIO_TEST_SCRIPT_TEMPLATE,
"sse" => templates::SSE_TEST_SCRIPT_TEMPLATE,
_ => {
return Err(GeneratorError::Template(format!(
"Unsupported transport type: {}. Supported types are 'stdio' and 'sse'. WebSocket transport is planned but not yet implemented.",
transport_type
)))
}
};
let content = test_script_template.replace("{{name}}", name);
fs::write(&test_script, content)?;
make_executable(&test_script)?;
Ok(())
}
fn make_executable(file_path: &Path) -> Result<(), GeneratorError> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(file_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(file_path, perms)?;
}
Ok(())
}