Skip to content

Commit c76d1e8

Browse files
committed
update deploy to retry if deployment failed
1 parent 65929ef commit c76d1e8

File tree

1 file changed

+106
-31
lines changed
  • src/mcp_agent/cli/cloud/commands/deploy

1 file changed

+106
-31
lines changed

src/mcp_agent/cli/cloud/commands/deploy/main.py

Lines changed: 106 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
from mcp_agent.cli.secrets.processor import (
2828
process_config_secrets,
2929
)
30+
from mcp_agent.cli.utils.retry import retry_async_with_exponential_backoff, RetryError
3031
from mcp_agent.cli.utils.ux import (
3132
console,
3233
print_deployment_header,
3334
print_error,
3435
print_info,
3536
print_success,
37+
print_warning,
3638
)
3739

3840
from .wrangler_wrapper import wrangler_deploy
@@ -84,6 +86,13 @@ def deploy_config(
8486
help="API key for authentication. Defaults to MCP_API_KEY environment variable.",
8587
envvar=ENV_API_KEY,
8688
),
89+
retry_count: int = typer.Option(
90+
3,
91+
"--retry-count",
92+
help="Number of retries on deployment failure.",
93+
min=1,
94+
max=10,
95+
),
8796
) -> str:
8897
"""Deploy an MCP agent using the specified configuration.
8998
@@ -101,6 +110,7 @@ def deploy_config(
101110
non_interactive: Never prompt for reusing or updating secrets or existing apps; reuse existing where possible
102111
api_url: API base URL
103112
api_key: API key for authentication
113+
retry_count: Number of retries on deployment failure
104114
105115
Returns:
106116
Newly-deployed MCP App ID
@@ -119,11 +129,13 @@ def deploy_config(
119129

120130
if not effective_api_url:
121131
raise CLIError(
122-
"MCP_API_BASE_URL environment variable or --api-url option must be set."
132+
"MCP_API_BASE_URL environment variable or --api-url option must be set.",
133+
retriable=False
123134
)
124135
if not effective_api_key:
125136
raise CLIError(
126-
"Must be logged in to deploy. Run 'mcp-agent login', set MCP_API_KEY environment variable or specify --api-key option."
137+
"Must be logged in to deploy. Run 'mcp-agent login', set MCP_API_KEY environment variable or specify --api-key option.",
138+
retriable=False
127139
)
128140
print_info(f"Using API at {effective_api_url}")
129141

@@ -166,7 +178,8 @@ def deploy_config(
166178
)
167179
except UnauthenticatedError as e:
168180
raise CLIError(
169-
"Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KEY environment variable with new API key."
181+
"Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KEY environment variable with new API key.",
182+
retriable=False
170183
) from e
171184
except Exception as e:
172185
raise CLIError(f"Error checking or creating app: {str(e)}") from e
@@ -237,47 +250,108 @@ def deploy_config(
237250
)
238251
)
239252

240-
wrangler_deploy(
253+
# Execute deployment with retry logic
254+
app = run_async(_deploy_with_retry(
241255
app_id=app_id,
242256
api_key=effective_api_key,
243257
project_dir=config_dir,
244-
)
258+
mcp_app_client=mcp_app_client,
259+
retry_count=retry_count,
260+
))
261+
262+
print_info(f"App ID: {app_id}")
263+
if app.appServerInfo:
264+
status = (
265+
"ONLINE"
266+
if app.appServerInfo.status == "APP_SERVER_STATUS_ONLINE"
267+
else "OFFLINE"
268+
)
269+
print_info(f"App URL: {app.appServerInfo.serverUrl}")
270+
print_info(f"App Status: {status}")
271+
return app_id
272+
273+
except Exception as e:
274+
if settings.VERBOSE:
275+
import traceback
276+
277+
typer.echo(traceback.format_exc())
278+
raise CLIError(f"Deployment failed: {str(e)}") from e
279+
280+
281+
async def _deploy_with_retry(
282+
app_id: str,
283+
api_key: str,
284+
project_dir: Path,
285+
mcp_app_client: MCPAppClient,
286+
retry_count: int,
287+
):
288+
"""Execute the deployment operations with retry logic.
289+
290+
Args:
291+
app_id: The application ID
292+
api_key: API key for authentication
293+
project_dir: Directory containing the project files
294+
mcp_app_client: MCP App client for API calls
295+
retry_count: Number of retry attempts
296+
297+
Returns:
298+
Deployed app information
299+
"""
300+
attempt = 0
301+
302+
async def _perform_deployment():
303+
nonlocal attempt
304+
attempt += 1
305+
306+
attempt_suffix = f" (attempt {attempt}/{retry_count})" if retry_count > 1 else ""
245307

246308
with Progress(
247309
SpinnerColumn(spinner_name="arrow3"),
248310
TextColumn("[progress.description]{task.description}"),
249311
) as progress:
250-
task = progress.add_task("Deploying MCP App bundle...", total=None)
251-
312+
# Bundle with wrangler
313+
bundle_task = progress.add_task(f"Bundling MCP Agent{attempt_suffix}...", total=None)
252314
try:
253-
app = run_async(
254-
mcp_app_client.deploy_app(
255-
app_id=app_id,
256-
)
315+
wrangler_deploy(
316+
app_id=app_id,
317+
api_key=api_key,
318+
project_dir=project_dir,
257319
)
258-
progress.update(task, description="✅ MCP App deployed successfully!")
259-
print_info(f"App ID: {app_id}")
260-
261-
if app.appServerInfo:
262-
status = (
263-
"ONLINE"
264-
if app.appServerInfo.status == "APP_SERVER_STATUS_ONLINE"
265-
else "OFFLINE"
266-
)
267-
print_info(f"App URL: {app.appServerInfo.serverUrl}")
268-
print_info(f"App Status: {status}")
269-
return app_id
320+
progress.update(bundle_task, description=f"✅ Bundled successfully{attempt_suffix}")
321+
except Exception as e:
322+
progress.update(bundle_task, description=f"❌ Bundling failed{attempt_suffix}")
323+
if attempt < retry_count:
324+
print_warning(f"Bundling failed on attempt {attempt}/{retry_count}: {str(e)}")
325+
raise
270326

327+
# Deploy app
328+
deploy_task = progress.add_task(f"Deploying MCP App bundle{attempt_suffix}...", total=None)
329+
try:
330+
app = await mcp_app_client.deploy_app(app_id=app_id)
331+
progress.update(deploy_task, description=f"✅ MCP App deployed successfully{attempt_suffix}!")
332+
return app
271333
except Exception as e:
272-
progress.update(task, description="❌ Deployment failed")
273-
raise e
334+
progress.update(deploy_task, description=f"❌ Deployment failed{attempt_suffix}")
335+
if attempt < retry_count:
336+
print_warning(f"Deployment failed on attempt {attempt}/{retry_count}: {str(e)}")
337+
raise
274338

275-
except Exception as e:
276-
if settings.VERBOSE:
277-
import traceback
339+
# Execute deployment with retry logic
340+
if retry_count > 1:
341+
print_info(f"Deployment configured with up to {retry_count} attempts")
278342

279-
typer.echo(traceback.format_exc())
280-
raise CLIError(f"Deployment failed: {str(e)}") from e
343+
try:
344+
return await retry_async_with_exponential_backoff(
345+
_perform_deployment,
346+
max_attempts=retry_count,
347+
initial_delay=1.0,
348+
backoff_multiplier=2.0,
349+
max_delay=30.0,
350+
)
351+
except RetryError as e:
352+
attempts_text = "attempts" if retry_count > 1 else "attempt"
353+
print_error(f"Deployment failed after {retry_count} {attempts_text}")
354+
raise CLIError(f"Deployment failed after {retry_count} {attempts_text}. Last error: {e.original_error}") from e.original_error
281355

282356

283357
def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[Path]]:
@@ -293,7 +367,8 @@ def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[P
293367
config_file = config_dir / MCP_CONFIG_FILENAME
294368
if not config_file.exists():
295369
raise CLIError(
296-
f"Configuration file '{MCP_CONFIG_FILENAME}' not found in {config_dir}"
370+
f"Configuration file '{MCP_CONFIG_FILENAME}' not found in {config_dir}",
371+
retriable=False
297372
)
298373

299374
secrets_file: Optional[Path] = None

0 commit comments

Comments
 (0)