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