2727from 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
3031from 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
3840from .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
283353def 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