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,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
283339def 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