Skip to main content

turbomcp_cli/
deploy.rs

1//! Deploy command implementation for MCP servers.
2//!
3//! Supports deploying to Cloudflare Workers and other edge platforms.
4
5use crate::cli::{BuildArgs, DeployArgs, WasmPlatform};
6use crate::error::{CliError, CliResult};
7use std::path::Path;
8use std::process::Command;
9
10/// Execute the deploy command.
11pub fn execute(args: &DeployArgs) -> CliResult<()> {
12    let project_path = args.path.canonicalize().map_err(|e| {
13        CliError::Other(format!(
14            "Failed to resolve project path '{}': {}",
15            args.path.display(),
16            e
17        ))
18    })?;
19
20    println!("Deploying MCP server to {}...", args.platform);
21
22    // Build first unless skipped
23    if !args.skip_build {
24        build_for_deploy(args, &project_path)?;
25    }
26
27    // Deploy based on platform
28    match args.platform {
29        WasmPlatform::CloudflareWorkers => deploy_cloudflare(args, &project_path),
30        WasmPlatform::DenoWorkers => deploy_deno(args, &project_path),
31        WasmPlatform::Wasm32 => Err(CliError::NotSupported(
32            "Generic WASM32 deployment is not supported. Please specify a platform.".to_string(),
33        )),
34    }
35}
36
37/// Build the project for deployment.
38fn build_for_deploy(args: &DeployArgs, project_path: &Path) -> CliResult<()> {
39    let build_args = BuildArgs {
40        path: project_path.to_path_buf(),
41        platform: Some(args.platform.clone()),
42        target: None,
43        release: args.release,
44        optimize: args.optimize,
45        features: vec![],
46        no_default_features: false,
47        output: None,
48    };
49
50    crate::build::execute(&build_args)
51}
52
53/// Deploy to Cloudflare Workers using wrangler.
54fn deploy_cloudflare(args: &DeployArgs, project_path: &Path) -> CliResult<()> {
55    // Check if wrangler is available
56    let wrangler_check = Command::new("wrangler").arg("--version").output();
57
58    if wrangler_check.is_err() {
59        return Err(CliError::Other(
60            "wrangler CLI not found. Install with: npm install -g wrangler".to_string(),
61        ));
62    }
63
64    // Determine wrangler config path
65    let config_path = args
66        .wrangler_config
67        .clone()
68        .unwrap_or_else(|| project_path.join("wrangler.toml"));
69
70    if !config_path.exists() {
71        return Err(CliError::Other(format!(
72            "wrangler.toml not found at '{}'. Create one or specify --wrangler-config",
73            config_path.display()
74        )));
75    }
76
77    // Build wrangler command
78    let mut cmd = Command::new("wrangler");
79    cmd.arg("deploy");
80    cmd.current_dir(project_path);
81
82    // Add config path if not default
83    if args.wrangler_config.is_some() {
84        cmd.arg("--config").arg(&config_path);
85    }
86
87    // Add environment if specified
88    if let Some(ref env) = args.env {
89        cmd.arg("--env").arg(env);
90    }
91
92    // Dry run
93    if args.dry_run {
94        cmd.arg("--dry-run");
95    }
96
97    println!(
98        "Running: wrangler deploy{}",
99        if args.dry_run { " --dry-run" } else { "" }
100    );
101
102    // Execute wrangler deploy
103    let status = cmd
104        .status()
105        .map_err(|e| CliError::Other(format!("Failed to execute wrangler: {}", e)))?;
106
107    if !status.success() {
108        return Err(CliError::Other("Wrangler deploy failed".to_string()));
109    }
110
111    if args.dry_run {
112        println!("Dry run complete. No changes were made.");
113    } else {
114        println!("Deployment successful!");
115    }
116
117    Ok(())
118}
119
120/// Deploy to Deno Deploy.
121fn deploy_deno(args: &DeployArgs, project_path: &Path) -> CliResult<()> {
122    // Check if deployctl is available
123    let deployctl_check = Command::new("deployctl").arg("--version").output();
124
125    if deployctl_check.is_err() {
126        return Err(CliError::Other(
127            "deployctl not found. Install with: deno install --allow-all --name=deployctl https://deno.land/x/deploy/deployctl.ts".to_string(),
128        ));
129    }
130
131    // Look for deno.json or main entry point
132    let config_path = project_path.join("deno.json");
133    let main_path = if config_path.exists() {
134        // Read config to find entry point
135        None // Will use default from config
136    } else {
137        // Look for common entry points
138        let candidates = ["main.ts", "mod.ts", "index.ts", "src/main.ts"];
139        candidates
140            .iter()
141            .map(|c| project_path.join(c))
142            .find(|p| p.exists())
143    };
144
145    // Build deployctl command
146    let mut cmd = Command::new("deployctl");
147    cmd.arg("deploy");
148    cmd.current_dir(project_path);
149
150    // Add entry point if found
151    if let Some(ref main) = main_path {
152        cmd.arg("--entrypoint").arg(main);
153    }
154
155    // Add environment (project name)
156    if let Some(ref env) = args.env {
157        cmd.arg("--project").arg(env);
158    }
159
160    // Dry run
161    if args.dry_run {
162        cmd.arg("--dry-run");
163    }
164
165    println!(
166        "Running: deployctl deploy{}",
167        if args.dry_run { " --dry-run" } else { "" }
168    );
169
170    // Execute deployctl
171    let status = cmd
172        .status()
173        .map_err(|e| CliError::Other(format!("Failed to execute deployctl: {}", e)))?;
174
175    if !status.success() {
176        return Err(CliError::Other("Deno Deploy failed".to_string()));
177    }
178
179    if args.dry_run {
180        println!("Dry run complete. No changes were made.");
181    } else {
182        println!("Deployment successful!");
183    }
184
185    Ok(())
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_platform_display() {
194        assert_eq!(
195            WasmPlatform::CloudflareWorkers.to_string(),
196            "cloudflare-workers"
197        );
198        assert_eq!(WasmPlatform::DenoWorkers.to_string(), "deno-workers");
199        assert_eq!(WasmPlatform::Wasm32.to_string(), "wasm32");
200    }
201}