Skip to content

Commit 01a0d8b

Browse files
committed
update deploy to retry if deployment failed
1 parent 79b1e41 commit 01a0d8b

File tree

1 file changed

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

1 file changed

+102
-31
lines changed

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

Lines changed: 102 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,104 @@ def deploy_config(
237250
)
238251
)
239252

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

246307
with Progress(
247308
SpinnerColumn(spinner_name="arrow3"),
248309
TextColumn("[progress.description]{task.description}"),
249310
) as progress:
250-
task = progress.add_task("Deploying MCP App bundle...", total=None)
251-
311+
bundle_task = progress.add_task(f"Bundling MCP Agent{attempt_suffix}...", total=None)
252312
try:
253-
app = run_async(
254-
mcp_app_client.deploy_app(
255-
app_id=app_id,
256-
)
313+
wrangler_deploy(
314+
app_id=app_id,
315+
api_key=api_key,
316+
project_dir=project_dir,
257317
)
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
318+
progress.update(bundle_task, description=f"✅ Bundled successfully{attempt_suffix}")
319+
except Exception as e:
320+
progress.update(bundle_task, description=f"❌ Bundling failed{attempt_suffix}")
321+
if attempt < retry_count:
322+
print_warning(f"Bundling failed on attempt {attempt}/{retry_count}: {str(e)}")
323+
raise
270324

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

275-
except Exception as e:
276-
if settings.VERBOSE:
277-
import traceback
336+
if retry_count > 1:
337+
print_info(f"Deployment configured with up to {retry_count} attempts")
278338

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

282352

283353
def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[Path]]:
@@ -293,7 +363,8 @@ def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[P
293363
config_file = config_dir / MCP_CONFIG_FILENAME
294364
if not config_file.exists():
295365
raise CLIError(
296-
f"Configuration file '{MCP_CONFIG_FILENAME}' not found in {config_dir}"
366+
f"Configuration file '{MCP_CONFIG_FILENAME}' not found in {config_dir}",
367+
retriable=False
297368
)
298369

299370
secrets_file: Optional[Path] = None

0 commit comments

Comments
 (0)