Skip to content

Commit a02b521

Browse files
committed
update deploy to retry if deployment failed
1 parent a3ed6b7 commit a02b521

File tree

1 file changed

+90
-33
lines changed
  • src/mcp_agent/cli/cloud/commands/deploy

1 file changed

+90
-33
lines changed

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

Lines changed: 90 additions & 33 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,90 @@ 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 attempt > 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+
deploy_task = progress.add_task(f"Deploying MCP App bundle{attempt_suffix}...", total=None)
252312
try:
253-
app = run_async(
254-
mcp_app_client.deploy_app(
255-
app_id=app_id,
256-
)
257-
)
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
270-
313+
app = await mcp_app_client.deploy_app(app_id=app_id)
314+
progress.update(deploy_task, description=f"✅ MCP App deployed successfully{attempt_suffix}!")
315+
return app
271316
except Exception as e:
272-
progress.update(task, description="❌ Deployment failed")
273-
raise e
317+
progress.update(deploy_task, description=f"❌ Deployment failed{attempt_suffix}")
318+
if attempt < retry_count:
319+
print_warning(f"Deployment failed on attempt {attempt}/{retry_count}: {str(e)}")
320+
raise
274321

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

279-
typer.echo(traceback.format_exc())
280-
raise CLIError(f"Deployment failed: {str(e)}") from e
325+
try:
326+
return await retry_async_with_exponential_backoff(
327+
_perform_deployment,
328+
max_attempts=retry_count,
329+
initial_delay=1.0,
330+
backoff_multiplier=2.0,
331+
max_delay=30.0,
332+
)
333+
except RetryError as e:
334+
attempts_text = "attempts" if retry_count > 1 else "attempt"
335+
print_error(f"Deployment failed after {retry_count} {attempts_text}")
336+
raise CLIError(f"Deployment failed after {retry_count} {attempts_text}. Last error: {e.original_error}") from e.original_error
281337

282338

283339
def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[Path]]:
@@ -293,7 +349,8 @@ def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[P
293349
config_file = config_dir / MCP_CONFIG_FILENAME
294350
if not config_file.exists():
295351
raise CLIError(
296-
f"Configuration file '{MCP_CONFIG_FILENAME}' not found in {config_dir}"
352+
f"Configuration file '{MCP_CONFIG_FILENAME}' not found in {config_dir}",
353+
retriable=False
297354
)
298355

299356
secrets_file: Optional[Path] = None

0 commit comments

Comments
 (0)