55"""
66
77from pathlib import Path
8+ from datetime import datetime , timezone
89from typing import Optional
910
1011import typer
3536 print_info ,
3637 print_success ,
3738)
39+ from mcp_agent .cli .utils .git_utils import (
40+ get_git_metadata ,
41+ create_git_tag ,
42+ sanitize_git_ref_component ,
43+ )
3844
3945from .wrangler_wrapper import wrangler_deploy
4046
@@ -85,6 +91,12 @@ def deploy_config(
8591 help = "API key for authentication. Defaults to MCP_API_KEY environment variable." ,
8692 envvar = ENV_API_KEY ,
8793 ),
94+ git_tag : bool = typer .Option (
95+ False ,
96+ "--git-tag/--no-git-tag" ,
97+ help = "Create a local git tag for this deploy (if in a git repo)" ,
98+ envvar = "MCP_DEPLOY_GIT_TAG" ,
99+ ),
88100 retry_count : int = typer .Option (
89101 3 ,
90102 "--retry-count" ,
@@ -129,12 +141,12 @@ def deploy_config(
129141 if not effective_api_url :
130142 raise CLIError (
131143 "MCP_API_BASE_URL environment variable or --api-url option must be set." ,
132- retriable = False
144+ retriable = False ,
133145 )
134146 if not effective_api_key :
135147 raise CLIError (
136148 "Must be logged in to deploy. Run 'mcp-agent login', set MCP_API_KEY environment variable or specify --api-key option." ,
137- retriable = False
149+ retriable = False ,
138150 )
139151 print_info (f"Using API at { effective_api_url } " )
140152
@@ -178,7 +190,7 @@ def deploy_config(
178190 except UnauthenticatedError as e :
179191 raise CLIError (
180192 "Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KEY environment variable with new API key." ,
181- retriable = False
193+ retriable = False ,
182194 ) from e
183195 except Exception as e :
184196 raise CLIError (f"Error checking or creating app: { str (e )} " ) from e
@@ -249,13 +261,36 @@ def deploy_config(
249261 )
250262 )
251263
252- app = run_async (_deploy_with_retry (
253- app_id = app_id ,
254- api_key = effective_api_key ,
255- project_dir = config_dir ,
256- mcp_app_client = mcp_app_client ,
257- retry_count = retry_count ,
258- ))
264+ # Optionally create a local git tag as a breadcrumb of this deployment
265+ if git_tag :
266+ git_meta = get_git_metadata (config_dir )
267+ if git_meta :
268+ # Sanitize app name for git tag safety
269+ safe_name = sanitize_git_ref_component (app_name )
270+ ts = datetime .now (timezone .utc ).strftime ("%Y%m%d-%H%M%S" )
271+ tag_name = f"mcp-deploy/{ safe_name } /{ ts } -{ git_meta .short_sha } "
272+ msg = (
273+ f"MCP Agent deploy for app '{ app_name } ' (id { app_id } )\n "
274+ f"Commit: { git_meta .commit_sha } \n "
275+ f"Branch: { git_meta .branch or '' } \n "
276+ f"Dirty: { git_meta .dirty } "
277+ )
278+ if create_git_tag (config_dir , tag_name , msg ):
279+ print_success (f"Created local git tag: { tag_name } " )
280+ else :
281+ print_info ("Skipping git tag (not a repo or tag failed)" )
282+ else :
283+ print_info ("Skipping git tag (not a git repository)" )
284+
285+ app = run_async (
286+ _deploy_with_retry (
287+ app_id = app_id ,
288+ api_key = effective_api_key ,
289+ project_dir = config_dir ,
290+ mcp_app_client = mcp_app_client ,
291+ retry_count = retry_count ,
292+ )
293+ )
259294
260295 print_info (f"App ID: { app_id } " )
261296 if app .appServerInfo :
@@ -318,13 +353,45 @@ async def _perform_api_deployment():
318353 SpinnerColumn (spinner_name = "arrow3" ),
319354 TextColumn ("[progress.description]{task.description}" ),
320355 ) as progress :
321- deploy_task = progress .add_task (f"Deploying MCP App bundle{ attempt_suffix } ..." , total = None )
356+ deploy_task = progress .add_task (
357+ f"Deploying MCP App bundle{ attempt_suffix } ..." , total = None
358+ )
322359 try :
323- app = await mcp_app_client .deploy_app (app_id = app_id )
324- progress .update (deploy_task , description = f"✅ MCP App deployed successfully{ attempt_suffix } !" )
360+ # Optionally include minimal metadata (git only to avoid heavy scans)
361+ metadata = None
362+ gm = get_git_metadata (project_dir )
363+ if gm :
364+ metadata = {
365+ "source" : "git" ,
366+ "commit" : gm .commit_sha ,
367+ "short" : gm .short_sha ,
368+ "branch" : gm .branch ,
369+ "dirty" : gm .dirty ,
370+ "tag" : gm .tag ,
371+ "message" : gm .commit_message ,
372+ }
373+
374+ try :
375+ app = await mcp_app_client .deploy_app (
376+ app_id = app_id , deployment_metadata = metadata
377+ )
378+ except Exception as e :
379+ # Fallback: if API rejects deploymentMetadata, retry once without it
380+ try :
381+ app = await mcp_app_client .deploy_app (
382+ app_id = app_id , deployment_metadata = None
383+ )
384+ except Exception :
385+ raise e
386+ progress .update (
387+ deploy_task ,
388+ description = f"✅ MCP App deployed successfully{ attempt_suffix } !" ,
389+ )
325390 return app
326391 except Exception :
327- progress .update (deploy_task , description = f"❌ Deployment failed{ attempt_suffix } " )
392+ progress .update (
393+ deploy_task , description = f"❌ Deployment failed{ attempt_suffix } "
394+ )
328395 raise
329396
330397 if retry_count > 1 :
@@ -341,7 +408,9 @@ async def _perform_api_deployment():
341408 except RetryError as e :
342409 attempts_text = "attempts" if retry_count > 1 else "attempt"
343410 print_error (f"Deployment failed after { retry_count } { attempts_text } " )
344- raise CLIError (f"Deployment failed after { retry_count } { attempts_text } . Last error: { e .original_error } " ) from e .original_error
411+ raise CLIError (
412+ f"Deployment failed after { retry_count } { attempts_text } . Last error: { e .original_error } "
413+ ) from e .original_error
345414
346415
347416def get_config_files (config_dir : Path ) -> tuple [Path , Optional [Path ], Optional [Path ]]:
@@ -358,7 +427,7 @@ def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[P
358427 if not config_file .exists ():
359428 raise CLIError (
360429 f"Configuration file '{ MCP_CONFIG_FILENAME } ' not found in { config_dir } " ,
361- retriable = False
430+ retriable = False ,
362431 )
363432
364433 secrets_file : Optional [Path ] = None
0 commit comments