1+ """Server workflows command implementation."""
2+
3+ import json
4+ from typing import Optional
5+
6+ import typer
7+ import yaml
8+ from rich .table import Table
9+
10+ from mcp_agent .cli .core .utils import run_async
11+ from mcp_agent .cli .mcp_app .api_client import WorkflowExecutionStatus
12+ from ...utils import (
13+ setup_authenticated_client ,
14+ validate_output_format ,
15+ handle_server_api_errors ,
16+ resolve_server ,
17+ )
18+ from mcp_agent .cli .utils .ux import console , print_info
19+
20+
21+ @handle_server_api_errors
22+ def list_workflows_for_server (
23+ app_id_or_config_id : str = typer .Argument (..., help = "App ID (app_xxx) or app config ID (apcnf_xxx) to list workflows for" ),
24+ limit : Optional [int ] = typer .Option (None , "--limit" , help = "Maximum number of results to return" ),
25+ status : Optional [str ] = typer .Option (None , "--status" , help = "Filter by status: running|failed|timed_out|canceled|terminated|completed|continued" ),
26+ format : Optional [str ] = typer .Option ("text" , "--format" , help = "Output format (text|json|yaml)" ),
27+ ) -> None :
28+ """List workflows for an MCP Server.
29+
30+ Examples:
31+
32+ mcp-agent cloud servers workflows app_abc123
33+
34+ mcp-agent cloud servers workflows apcnf_xyz789 --status running
35+
36+ mcp-agent cloud servers workflows app_abc123 --limit 10 --format json
37+ """
38+ validate_output_format (format )
39+ client = setup_authenticated_client ()
40+
41+ server = None
42+ try :
43+ server = resolve_server (client , app_id_or_config_id )
44+ except Exception :
45+ pass
46+
47+ max_results = limit or 100
48+
49+ status_filter = None
50+ if status :
51+ status_map = {
52+ "running" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_RUNNING ,
53+ "failed" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_FAILED ,
54+ "timed_out" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_TIMED_OUT ,
55+ "timeout" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_TIMED_OUT , # alias
56+ "canceled" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_CANCELED ,
57+ "cancelled" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_CANCELED , # alias
58+ "terminated" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_TERMINATED ,
59+ "completed" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_COMPLETED ,
60+ "continued" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW ,
61+ "continued_as_new" : WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW ,
62+ }
63+ status_filter = status_map .get (status .lower ())
64+ if not status_filter :
65+ valid_statuses = "running|failed|timed_out|timeout|canceled|cancelled|terminated|completed|continued|continued_as_new"
66+ raise typer .BadParameter (f"Invalid status '{ status } '. Valid options: { valid_statuses } " )
67+
68+ async def list_workflows_async ():
69+ return await client .list_workflows (
70+ app_id_or_config_id = app_id_or_config_id ,
71+ max_results = max_results
72+ )
73+
74+ response = run_async (list_workflows_async ())
75+ workflows = response .workflows or []
76+
77+ if status_filter :
78+ workflows = [w for w in workflows if w .execution_status == status_filter ]
79+
80+ if format == "json" :
81+ _print_workflows_json (workflows )
82+ elif format == "yaml" :
83+ _print_workflows_yaml (workflows )
84+ else :
85+ _print_workflows_text (workflows , server , status , app_id_or_config_id )
86+
87+
88+ def _print_workflows_text (workflows , server , status_filter , app_id_or_config_id ):
89+ """Print workflows in text format."""
90+ if server and hasattr (server , 'name' ) and server .name :
91+ server_name = server .name
92+ else :
93+ server_name = app_id_or_config_id
94+
95+ console .print (f"\n [bold blue]📊 Workflows for Server: { server_name } [/bold blue]" )
96+
97+ if not workflows :
98+ print_info ("No workflows found for this server." )
99+ return
100+
101+ console .print (f"\n Found { len (workflows )} workflow(s):" )
102+
103+ table = Table (show_header = True , header_style = "bold blue" )
104+ table .add_column ("Workflow ID" , style = "cyan" , width = 20 )
105+ table .add_column ("Name" , style = "green" , width = 20 )
106+ table .add_column ("Status" , style = "yellow" , width = 15 )
107+ table .add_column ("Run ID" , style = "dim" , width = 15 )
108+ table .add_column ("Created" , style = "dim" , width = 20 )
109+ table .add_column ("Principal" , style = "dim" , width = 15 )
110+
111+ for workflow in workflows :
112+ status_display = _get_status_display (workflow .execution_status )
113+ created_display = workflow .created_at .strftime ('%Y-%m-%d %H:%M:%S' ) if workflow .created_at else "N/A"
114+ run_id_display = _truncate_string (workflow .run_id or "N/A" , 15 )
115+
116+ table .add_row (
117+ _truncate_string (workflow .workflow_id , 20 ),
118+ _truncate_string (workflow .name , 20 ),
119+ status_display ,
120+ run_id_display ,
121+ created_display ,
122+ _truncate_string (workflow .principal_id , 15 ),
123+ )
124+
125+ console .print (table )
126+
127+ if status_filter :
128+ console .print (f"\n [dim]Filtered by status: { status_filter } [/dim]" )
129+
130+
131+ def _print_workflows_json (workflows ):
132+ """Print workflows in JSON format."""
133+ workflows_data = [_workflow_to_dict (workflow ) for workflow in workflows ]
134+ print (json .dumps ({"workflows" : workflows_data }, indent = 2 , default = str ))
135+
136+
137+ def _print_workflows_yaml (workflows ):
138+ """Print workflows in YAML format."""
139+ workflows_data = [_workflow_to_dict (workflow ) for workflow in workflows ]
140+ print (yaml .dump ({"workflows" : workflows_data }, default_flow_style = False ))
141+
142+
143+ def _workflow_to_dict (workflow ):
144+ """Convert WorkflowInfo to dictionary."""
145+ return {
146+ "workflow_id" : workflow .workflow_id ,
147+ "run_id" : workflow .run_id ,
148+ "name" : workflow .name ,
149+ "created_at" : workflow .created_at .isoformat () if workflow .created_at else None ,
150+ "principal_id" : workflow .principal_id ,
151+ "execution_status" : workflow .execution_status .value if workflow .execution_status else None ,
152+ }
153+
154+
155+ def _truncate_string (text : str , max_length : int ) -> str :
156+ """Truncate string to max_length, adding ellipsis if truncated."""
157+ if len (text ) <= max_length :
158+ return text
159+ return text [:max_length - 3 ] + "..."
160+
161+
162+ def _get_status_display (status ):
163+ """Convert WorkflowExecutionStatus to display string with emoji."""
164+ if not status :
165+ return "❓ Unknown"
166+
167+ status_map = {
168+ WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_RUNNING : "[green]🟢 Running[/green]" ,
169+ WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_COMPLETED : "[blue]✅ Completed[/blue]" ,
170+ WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_FAILED : "[red]❌ Failed[/red]" ,
171+ WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_CANCELED : "[yellow]🟡 Canceled[/yellow]" ,
172+ WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_TERMINATED : "[red]🔴 Terminated[/red]" ,
173+ WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_TIMED_OUT : "[orange]⏰ Timed Out[/orange]" ,
174+ WorkflowExecutionStatus .WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW : "[purple]🔄 Continued[/purple]" ,
175+ }
176+
177+ return status_map .get (status , "❓ Unknown" )
0 commit comments