diff --git a/src/mcp_agent/cli/cloud/commands/logger/tail/main.py b/src/mcp_agent/cli/cloud/commands/logger/tail/main.py index 9886fedbd..7d099d43e 100644 --- a/src/mcp_agent/cli/cloud/commands/logger/tail/main.py +++ b/src/mcp_agent/cli/cloud/commands/logger/tail/main.py @@ -17,7 +17,7 @@ from mcp_agent.cli.exceptions import CLIError from mcp_agent.cli.auth import load_credentials, UserCredentials -from mcp_agent.cli.cloud.commands.servers.utils import setup_authenticated_client +from mcp_agent.cli.cloud.commands.utils import setup_authenticated_client from mcp_agent.cli.core.api_client import UnauthenticatedError from mcp_agent.cli.core.utils import parse_app_identifier, resolve_server_url from mcp_agent.cli.utils.ux import print_error diff --git a/src/mcp_agent/cli/cloud/commands/servers/delete/main.py b/src/mcp_agent/cli/cloud/commands/servers/delete/main.py index 67cb79ad6..48d9ab04d 100644 --- a/src/mcp_agent/cli/cloud/commands/servers/delete/main.py +++ b/src/mcp_agent/cli/cloud/commands/servers/delete/main.py @@ -5,7 +5,7 @@ from mcp_agent.cli.core.utils import run_async from mcp_agent.cli.exceptions import CLIError from mcp_agent.cli.mcp_app.api_client import MCPApp -from ..utils import ( +from ...utils import ( setup_authenticated_client, resolve_server, handle_server_api_errors, diff --git a/src/mcp_agent/cli/cloud/commands/servers/describe/main.py b/src/mcp_agent/cli/cloud/commands/servers/describe/main.py index b5b410c7b..ced0c8403 100644 --- a/src/mcp_agent/cli/cloud/commands/servers/describe/main.py +++ b/src/mcp_agent/cli/cloud/commands/servers/describe/main.py @@ -7,7 +7,7 @@ from mcp_agent.cli.exceptions import CLIError from mcp_agent.cli.mcp_app.api_client import MCPApp, MCPAppConfiguration -from ..utils import ( +from ...utils import ( setup_authenticated_client, validate_output_format, resolve_server, diff --git a/src/mcp_agent/cli/cloud/commands/servers/list/main.py b/src/mcp_agent/cli/cloud/commands/servers/list/main.py index 1ea27740c..a184375c8 100644 --- a/src/mcp_agent/cli/cloud/commands/servers/list/main.py +++ b/src/mcp_agent/cli/cloud/commands/servers/list/main.py @@ -8,7 +8,7 @@ from mcp_agent.cli.core.utils import run_async from mcp_agent.cli.mcp_app.api_client import MCPApp, MCPAppConfiguration -from ..utils import ( +from ...utils import ( setup_authenticated_client, validate_output_format, handle_server_api_errors, diff --git a/src/mcp_agent/cli/cloud/commands/servers/utils.py b/src/mcp_agent/cli/cloud/commands/utils.py similarity index 98% rename from src/mcp_agent/cli/cloud/commands/servers/utils.py rename to src/mcp_agent/cli/cloud/commands/utils.py index bc7d3db8d..df258afe2 100644 --- a/src/mcp_agent/cli/cloud/commands/servers/utils.py +++ b/src/mcp_agent/cli/cloud/commands/utils.py @@ -1,4 +1,4 @@ -"""Shared utilities for server commands.""" +"""Shared utilities for cloud commands.""" from functools import wraps from typing import Union diff --git a/src/mcp_agent/cli/cloud/commands/workflow/__init__.py b/src/mcp_agent/cli/cloud/commands/workflow/__init__.py deleted file mode 100644 index f14dc4ee7..000000000 --- a/src/mcp_agent/cli/cloud/commands/workflow/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""MCP Agent Cloud workflow command.""" - -from .status import get_workflow_status - -__all__ = ["get_workflow_status"] diff --git a/src/mcp_agent/cli/cloud/commands/workflow/status/__init__.py b/src/mcp_agent/cli/cloud/commands/workflow/status/__init__.py deleted file mode 100644 index 323c8b718..000000000 --- a/src/mcp_agent/cli/cloud/commands/workflow/status/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""MCP Agent Cloud workflow status.""" - -from .main import get_workflow_status - -__all__ = ["get_workflow_status"] diff --git a/src/mcp_agent/cli/cloud/commands/workflow/status/main.py b/src/mcp_agent/cli/cloud/commands/workflow/status/main.py deleted file mode 100644 index 702830ded..000000000 --- a/src/mcp_agent/cli/cloud/commands/workflow/status/main.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Optional - -import typer -from rich.panel import Panel - -from mcp_agent.cli.auth import load_api_key_credentials -from mcp_agent.cli.config import settings -from mcp_agent.cli.core.api_client import UnauthenticatedError -from mcp_agent.cli.core.constants import ENV_API_BASE_URL, ENV_API_KEY -from mcp_agent.cli.core.utils import run_async -from mcp_agent.cli.exceptions import CLIError -from mcp_agent.cli.utils.ux import console -from mcp_agent.cli.workflows.api_client import ( - WorkflowAPIClient, - WorkflowInfo, -) - - -def get_workflow_status( - workflow_id: str = typer.Option( - None, - "--id", - "-i", - help="ID of the workflow to get details for.", - ), - api_url: Optional[str] = typer.Option( - settings.API_BASE_URL, - "--api-url", - help="API base URL. Defaults to MCP_API_BASE_URL environment variable.", - envvar=ENV_API_BASE_URL, - ), - api_key: Optional[str] = typer.Option( - settings.API_KEY, - "--api-key", - help="API key for authentication. Defaults to MCP_API_KEY environment variable.", - envvar=ENV_API_KEY, - ), -) -> None: - """Get workflow status.""" - effective_api_key = api_key or settings.API_KEY or load_api_key_credentials() - - if not effective_api_key: - raise CLIError( - "Must be logged in to get workflow status. Run 'mcp-agent login', set MCP_API_KEY environment variable or specify --api-key option." - ) - - api_url = ( - api_url or settings.API_BASE_URL - ) # already defaulted via typer, but making the type checker happy here - client = WorkflowAPIClient(api_url=api_url, api_key=effective_api_key) - - if not workflow_id: - raise CLIError("You must provide a workflow ID to get its status.") - - try: - workflow_info = run_async(client.get_workflow(workflow_id)) - - if not workflow_info: - raise CLIError(f"Workflow with ID '{workflow_id}' not found.") - - print_workflow_info(workflow_info) - - except UnauthenticatedError as e: - raise CLIError( - "Invalid API key. Run 'mcp-agent login' or set MCP_API_KEY environment variable with new API key." - ) from e - except Exception as e: - raise CLIError( - f"Error getting status for workflow with ID {workflow_id}: {str(e)}" - ) from e - - -def print_workflow_info(workflow_info: WorkflowInfo) -> None: - console.print( - Panel( - f"Name: [cyan]{workflow_info.name}[/cyan]\n" - f"ID: [cyan]{workflow_info.workflowId}[/cyan]\n" - f"Run ID: [cyan]{workflow_info.runId or 'N/A'}[/cyan]\n" - f"Created: [cyan]{workflow_info.createdAt.strftime('%Y-%m-%d %H:%M:%S')}[/cyan]\n" - f"Status: [cyan]{_execution_status_text(workflow_info.executionStatus)}[/cyan]", - title="Workflow", - border_style="blue", - expand=False, - ) - ) - - -def _execution_status_text(status: Optional[str]) -> str: - """Format the execution status text.""" - match status: - case "WORKFLOW_EXECUTION_STATUS_RUNNING": - return "🔄 Running" - case "WORKFLOW_EXECUTION_STATUS_FAILED": - return "❌ Failed" - case "WORKFLOW_EXECUTION_STATUS_TIMED_OUT": - return "⌛ Timed Out" - case "WORKFLOW_EXECUTION_STATUS_CANCELLED": - return "🚫 Cancelled" - case "WORKFLOW_EXECUTION_STATUS_TERMINATED": - return "🛑 Terminated" - case "WORKFLOW_EXECUTION_STATUS_COMPLETED": - return "✅ Completed" - case "WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW": - return "🔁 Continued as New" - case _: - return "❓ Unknown" diff --git a/src/mcp_agent/cli/cloud/commands/workflows/__init__.py b/src/mcp_agent/cli/cloud/commands/workflows/__init__.py new file mode 100644 index 000000000..5ecd86ba4 --- /dev/null +++ b/src/mcp_agent/cli/cloud/commands/workflows/__init__.py @@ -0,0 +1,12 @@ +"""MCP Agent Cloud workflows commands.""" + +from .describe import describe_workflow +from .resume import resume_workflow, suspend_workflow +from .cancel import cancel_workflow + +__all__ = [ + "describe_workflow", + "resume_workflow", + "suspend_workflow", + "cancel_workflow", +] diff --git a/src/mcp_agent/cli/cloud/commands/workflows/cancel/__init__.py b/src/mcp_agent/cli/cloud/commands/workflows/cancel/__init__.py new file mode 100644 index 000000000..deb13e29e --- /dev/null +++ b/src/mcp_agent/cli/cloud/commands/workflows/cancel/__init__.py @@ -0,0 +1,5 @@ +"""MCP Agent Cloud workflow cancel command.""" + +from .main import cancel_workflow + +__all__ = ["cancel_workflow"] diff --git a/src/mcp_agent/cli/cloud/commands/workflows/cancel/main.py b/src/mcp_agent/cli/cloud/commands/workflows/cancel/main.py new file mode 100644 index 000000000..5131c6b58 --- /dev/null +++ b/src/mcp_agent/cli/cloud/commands/workflows/cancel/main.py @@ -0,0 +1,87 @@ +"""Workflow cancel command implementation.""" + +from typing import Optional + +import typer + +from mcp_agent.app import MCPApp +from mcp_agent.cli.core.utils import run_async +from mcp_agent.cli.exceptions import CLIError +from mcp_agent.cli.utils.ux import console +from mcp_agent.config import MCPServerSettings, Settings, LoggerSettings +from mcp_agent.mcp.gen_client import gen_client +from ...utils import setup_authenticated_client, resolve_server, handle_server_api_errors + + +async def _cancel_workflow_async( + server_id_or_url: str, + run_id: str, + reason: Optional[str] = None +) -> None: + """Cancel a workflow using MCP tool calls to a deployed server.""" + if server_id_or_url.startswith(('http://', 'https://')): + server_url = server_id_or_url + else: + client = setup_authenticated_client() + server = resolve_server(client, server_id_or_url) + + if hasattr(server, 'appServerInfo') and server.appServerInfo: + server_url = server.appServerInfo.serverUrl + else: + raise CLIError(f"Server '{server_id_or_url}' is not deployed or has no server URL") + + if not server_url: + raise CLIError(f"No server URL found for server '{server_id_or_url}'") + + quiet_settings = Settings(logger=LoggerSettings(level="error")) + app = MCPApp(name="workflows_cli", settings=quiet_settings) + + try: + async with app.run() as workflow_app: + context = workflow_app.context + + sse_url = f"{server_url.rstrip('/')}/sse" if not server_url.endswith('/sse') else server_url + context.server_registry.registry["workflow_server"] = MCPServerSettings( + name="workflow_server", + description=f"Deployed MCP server {server_url}", + url=sse_url, + transport="sse" + ) + + async with gen_client("workflow_server", server_registry=context.server_registry) as client: + tool_params = {"run_id": run_id} + + result = await client.call_tool("workflows-cancel", tool_params) + + success = result.content[0].text if result.content else False + if isinstance(success, str): + success = success.lower() == 'true' + + if success: + console.print("[yellow]⚠[/yellow] Successfully cancelled workflow") + console.print(f" Run ID: [cyan]{run_id}[/cyan]") + if reason: + console.print(f" Reason: [dim]{reason}[/dim]") + else: + raise CLIError(f"Failed to cancel workflow with run ID {run_id}") + + except Exception as e: + raise CLIError(f"Error cancelling workflow with run ID {run_id}: {str(e)}") from e + + +@handle_server_api_errors +def cancel_workflow( + server_id_or_url: str = typer.Argument(..., help="Server ID or URL hosting the workflow"), + run_id: str = typer.Argument(..., help="Run ID of the workflow to cancel"), + reason: Optional[str] = typer.Option(None, "--reason", help="Optional reason for cancellation"), +) -> None: + """Cancel a workflow execution. + + Permanently stops a workflow execution. Unlike suspend, a cancelled workflow + cannot be resumed and will be marked as cancelled. + + Examples: + mcp-agent cloud workflows cancel app_abc123 run_xyz789 + mcp-agent cloud workflows cancel https://server.example.com run_xyz789 --reason "User requested cancellation" + """ + run_async(_cancel_workflow_async(server_id_or_url, run_id, reason)) diff --git a/src/mcp_agent/cli/cloud/commands/workflows/describe/__init__.py b/src/mcp_agent/cli/cloud/commands/workflows/describe/__init__.py new file mode 100644 index 000000000..10e2756cd --- /dev/null +++ b/src/mcp_agent/cli/cloud/commands/workflows/describe/__init__.py @@ -0,0 +1,5 @@ +"""MCP Agent Cloud workflow describe command.""" + +from .main import describe_workflow + +__all__ = ["describe_workflow"] diff --git a/src/mcp_agent/cli/cloud/commands/workflows/describe/main.py b/src/mcp_agent/cli/cloud/commands/workflows/describe/main.py new file mode 100644 index 000000000..4e7a9f388 --- /dev/null +++ b/src/mcp_agent/cli/cloud/commands/workflows/describe/main.py @@ -0,0 +1,148 @@ +"""Workflow describe command implementation.""" + +import json +from typing import Optional + +import typer +import yaml +from rich.panel import Panel + +from mcp_agent.app import MCPApp +from mcp_agent.cli.core.utils import run_async +from mcp_agent.cli.exceptions import CLIError +from mcp_agent.cli.utils.ux import console +from mcp_agent.config import MCPServerSettings, Settings, LoggerSettings +from mcp_agent.mcp.gen_client import gen_client +from ...utils import setup_authenticated_client, resolve_server, handle_server_api_errors + + +async def _describe_workflow_async( + server_id_or_url: str, + run_id: str, + format: str = "text" +) -> None: + """Describe a workflow using MCP tool calls to a deployed server.""" + if server_id_or_url.startswith(('http://', 'https://')): + server_url = server_id_or_url + else: + client = setup_authenticated_client() + server = resolve_server(client, server_id_or_url) + + if hasattr(server, 'appServerInfo') and server.appServerInfo: + server_url = server.appServerInfo.serverUrl + else: + raise CLIError(f"Server '{server_id_or_url}' is not deployed or has no server URL") + + if not server_url: + raise CLIError(f"No server URL found for server '{server_id_or_url}'") + + quiet_settings = Settings(logger=LoggerSettings(level="error")) + app = MCPApp(name="workflows_cli", settings=quiet_settings) + + try: + async with app.run() as workflow_app: + context = workflow_app.context + + sse_url = f"{server_url}/sse" if not server_url.endswith('/sse') else server_url + context.server_registry.registry["workflow_server"] = MCPServerSettings( + name="workflow_server", + description=f"Deployed MCP server {server_url}", + url=sse_url, + transport="sse" + ) + + async with gen_client("workflow_server", server_registry=context.server_registry) as client: + result = await client.call_tool("workflows-get_status", { + "run_id": run_id + }) + + workflow_status = result.content[0].text if result.content else {} + if isinstance(workflow_status, str): + workflow_status = json.loads(workflow_status) + + if not workflow_status: + raise CLIError(f"Workflow with run ID '{run_id}' not found.") + + if format == "json": + print(json.dumps(workflow_status, indent=2)) + elif format == "yaml": + print(yaml.dump(workflow_status, default_flow_style=False)) + else: # text format + print_workflow_status(workflow_status) + + except Exception as e: + raise CLIError(f"Error describing workflow with run ID {run_id}: {str(e)}") from e + + +@handle_server_api_errors +def describe_workflow( + server_id_or_url: str = typer.Argument(..., help="Server ID or URL hosting the workflow"), + run_id: str = typer.Argument(..., help="Run ID of the workflow to describe"), + format: Optional[str] = typer.Option("text", "--format", help="Output format (text|json|yaml)"), +) -> None: + """Describe a workflow execution (alias: status). + + Shows detailed information about a workflow execution including its current status, + creation time, and other metadata. + + Examples: + mcp-agent cloud workflows describe app_abc123 run_xyz789 + mcp-agent cloud workflows describe https://server.example.com run_xyz789 --format json + """ + if format not in ["text", "json", "yaml"]: + console.print("[red]Error: --format must be 'text', 'json', or 'yaml'[/red]") + raise typer.Exit(6) + + run_async(_describe_workflow_async(server_id_or_url, run_id, format)) + + +def print_workflow_status(workflow_status: dict) -> None: + """Print workflow status information in text format.""" + name = workflow_status.get("name", "N/A") + workflow_id = workflow_status.get("workflow_id", workflow_status.get("workflowId", "N/A")) + run_id = workflow_status.get("run_id", workflow_status.get("runId", "N/A")) + status = workflow_status.get("status", "N/A") + + created_at = workflow_status.get("created_at", workflow_status.get("createdAt", "N/A")) + if created_at != "N/A" and isinstance(created_at, str): + try: + from datetime import datetime + created_dt = datetime.fromisoformat(created_at.replace('Z', '+00:00')) + created_at = created_dt.strftime('%Y-%m-%d %H:%M:%S') + except (ValueError, TypeError): + pass # Keep original format if parsing fails + + console.print( + Panel( + f"Name: [cyan]{name}[/cyan]\n" + f"Workflow ID: [cyan]{workflow_id}[/cyan]\n" + f"Run ID: [cyan]{run_id}[/cyan]\n" + f"Created: [cyan]{created_at}[/cyan]\n" + f"Status: [cyan]{_format_status(status)}[/cyan]", + title="Workflow", + border_style="blue", + expand=False, + ) + ) + + +def _format_status(status: str) -> str: + """Format the execution status text.""" + status_lower = str(status).lower() + + if "running" in status_lower: + return "🔄 Running" + elif "failed" in status_lower or "error" in status_lower: + return "❌ Failed" + elif "timeout" in status_lower or "timed_out" in status_lower: + return "⌛ Timed Out" + elif "cancel" in status_lower: + return "🚫 Cancelled" + elif "terminat" in status_lower: + return "🛑 Terminated" + elif "complet" in status_lower: + return "✅ Completed" + elif "continued" in status_lower: + return "🔁 Continued as New" + else: + return f"❓ {status}" diff --git a/src/mcp_agent/cli/cloud/commands/workflows/resume/__init__.py b/src/mcp_agent/cli/cloud/commands/workflows/resume/__init__.py new file mode 100644 index 000000000..23b3d80f3 --- /dev/null +++ b/src/mcp_agent/cli/cloud/commands/workflows/resume/__init__.py @@ -0,0 +1,5 @@ +"""MCP Agent Cloud workflow resume and suspend commands.""" + +from .main import resume_workflow, suspend_workflow + +__all__ = ["resume_workflow", "suspend_workflow"] diff --git a/src/mcp_agent/cli/cloud/commands/workflows/resume/main.py b/src/mcp_agent/cli/cloud/commands/workflows/resume/main.py new file mode 100644 index 000000000..4fea41fdd --- /dev/null +++ b/src/mcp_agent/cli/cloud/commands/workflows/resume/main.py @@ -0,0 +1,126 @@ +"""Workflow resume command implementation.""" + +import json +from typing import Optional + +import typer + +from mcp_agent.app import MCPApp +from mcp_agent.cli.core.utils import run_async +from mcp_agent.cli.exceptions import CLIError +from mcp_agent.cli.utils.ux import console +from mcp_agent.config import MCPServerSettings, Settings, LoggerSettings +from mcp_agent.mcp.gen_client import gen_client +from ...utils import setup_authenticated_client, resolve_server, handle_server_api_errors + + +async def _signal_workflow_async( + server_id_or_url: str, + run_id: str, + signal_name: str = "resume", + payload: Optional[str] = None +) -> None: + """Send a signal to a workflow using MCP tool calls to a deployed server.""" + if server_id_or_url.startswith(('http://', 'https://')): + server_url = server_id_or_url + else: + client = setup_authenticated_client() + server = resolve_server(client, server_id_or_url) + + if hasattr(server, 'appServerInfo') and server.appServerInfo: + server_url = server.appServerInfo.serverUrl + else: + raise CLIError(f"Server '{server_id_or_url}' is not deployed or has no server URL") + + if not server_url: + raise CLIError(f"No server URL found for server '{server_id_or_url}'") + + quiet_settings = Settings(logger=LoggerSettings(level="error")) + app = MCPApp(name="workflows_cli", settings=quiet_settings) + + try: + async with app.run() as workflow_app: + context = workflow_app.context + + sse_url = f"{server_url.rstrip('/')}/sse" if not server_url.endswith('/sse') else server_url + context.server_registry.registry["workflow_server"] = MCPServerSettings( + name="workflow_server", + description=f"Deployed MCP server {server_url}", + url=sse_url, + transport="sse" + ) + + async with gen_client("workflow_server", server_registry=context.server_registry) as client: + tool_params = {"run_id": run_id, "signal_name": signal_name} + if payload: + tool_params["payload"] = payload + + result = await client.call_tool("workflows-resume", tool_params) + + success = result.content[0].text if result.content else False + if isinstance(success, str): + success = success.lower() == 'true' + + if success: + action_past = "resumed" if signal_name == "resume" else "suspended" if signal_name == "suspend" else f"signaled ({signal_name})" + action_color = "green" if signal_name == "resume" else "yellow" if signal_name == "suspend" else "blue" + action_icon = "✓" if signal_name == "resume" else "⏸" if signal_name == "suspend" else "📡" + console.print(f"[{action_color}]{action_icon}[/{action_color}] Successfully {action_past} workflow") + console.print(f" Run ID: [cyan]{run_id}[/cyan]") + else: + raise CLIError(f"Failed to {signal_name} workflow with run ID {run_id}") + + except Exception as e: + raise CLIError(f"Error {signal_name}ing workflow with run ID {run_id}: {str(e)}") from e + + +@handle_server_api_errors +def resume_workflow( + server_id_or_url: str = typer.Argument(..., help="Server ID or URL hosting the workflow"), + run_id: str = typer.Argument(..., help="Run ID of the workflow to resume"), + payload: Optional[str] = typer.Option(None, "--payload", help="JSON or text payload to pass to resumed workflow"), +) -> None: + """Resume a suspended workflow execution. + + Resumes execution of a previously suspended workflow. Optionally accepts + a payload (JSON or text) to pass data to the resumed workflow. + + Examples: + mcp-agent cloud workflows resume app_abc123 run_xyz789 + mcp-agent cloud workflows resume https://server.example.com run_xyz789 --payload '{"data": "value"}' + mcp-agent cloud workflows resume app_abc123 run_xyz789 --payload "simple text" + """ + if payload: + try: + json.loads(payload) + console.print("[dim]Resuming with JSON payload...[/dim]") + except json.JSONDecodeError: + console.print("[dim]Resuming with text payload...[/dim]") + + run_async(_signal_workflow_async(server_id_or_url, run_id, "resume", payload)) + + +@handle_server_api_errors +def suspend_workflow( + server_id_or_url: str = typer.Argument(..., help="Server ID or URL hosting the workflow"), + run_id: str = typer.Argument(..., help="Run ID of the workflow to suspend"), + payload: Optional[str] = typer.Option(None, "--payload", help="JSON or text payload to pass to suspended workflow"), +) -> None: + """Suspend a workflow execution. + + Temporarily pauses a workflow execution, which can later be resumed. + Optionally accepts a payload (JSON or text) to pass data to the suspended workflow. + + Examples: + mcp-agent cloud workflows suspend app_abc123 run_xyz789 + mcp-agent cloud workflows suspend https://server.example.com run_xyz789 --payload '{"reason": "maintenance"}' + mcp-agent cloud workflows suspend app_abc123 run_xyz789 --payload "paused for review" + """ + if payload: + try: + json.loads(payload) + console.print("[dim]Suspending with JSON payload...[/dim]") + except json.JSONDecodeError: + console.print("[dim]Suspending with text payload...[/dim]") + + run_async(_signal_workflow_async(server_id_or_url, run_id, "suspend", payload)) diff --git a/src/mcp_agent/cli/cloud/main.py b/src/mcp_agent/cli/cloud/main.py index 171d8e28f..8de0dfe42 100644 --- a/src/mcp_agent/cli/cloud/main.py +++ b/src/mcp_agent/cli/cloud/main.py @@ -27,7 +27,12 @@ list_app_workflows, ) from mcp_agent.cli.cloud.commands.apps import list_apps -from mcp_agent.cli.cloud.commands.workflow import get_workflow_status +from mcp_agent.cli.cloud.commands.workflows import ( + describe_workflow, + resume_workflow, + suspend_workflow, + cancel_workflow, +) from mcp_agent.cli.cloud.commands.servers import ( list_servers, describe_server, @@ -137,14 +142,17 @@ def invoke(self, ctx): app_cmd_app.command(name="workflows")(list_app_workflows) app.add_typer(app_cmd_app, name="app", help="Manage an MCP App") -# Sub-typer for `mcp-agent workflow` commands -app_cmd_workflow = typer.Typer( +# Sub-typer for `mcp-agent workflows` commands +app_cmd_workflows = typer.Typer( help="Management commands for MCP Workflows", no_args_is_help=True, cls=HelpfulTyperGroup, ) -app_cmd_workflow.command(name="status")(get_workflow_status) -app.add_typer(app_cmd_workflow, name="workflow", help="Manage MCP Workflows") +app_cmd_workflows.command(name="describe")(describe_workflow) +app_cmd_workflows.command(name="status")(describe_workflow) # alias for describe +app_cmd_workflows.command(name="resume")(resume_workflow) +app_cmd_workflows.command(name="suspend")(suspend_workflow) +app_cmd_workflows.command(name="cancel")(cancel_workflow) # Sub-typer for `mcp-agent servers` commands app_cmd_servers = typer.Typer( @@ -201,6 +209,9 @@ def invoke(self, ctx): app_cmd_cloud.add_typer( app_cmd_cloud_logger, name="logger", help="Logging and observability" ) +app_cmd_cloud.add_typer( + app_cmd_workflows, name="workflows", help="Workflow management commands" +) app_cmd_cloud.add_typer( app_cmd_servers, name="servers", help="Server management commands" ) diff --git a/uv.lock b/uv.lock index 09b0c7340..32596e522 100644 --- a/uv.lock +++ b/uv.lock @@ -2045,7 +2045,6 @@ source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "fastapi" }, - { name = "instructor" }, { name = "jsonref" }, { name = "mcp" }, { name = "numpy" }, @@ -2138,7 +2137,6 @@ requires-dist = [ { name = "google-genai", marker = "extra == 'google'", specifier = ">=1.10.0" }, { name = "httpx", marker = "extra == 'cli'", specifier = ">=0.28.1" }, { name = "hvac", marker = "extra == 'cli'", specifier = ">=1.1.1" }, - { name = "instructor", specifier = ">=1.7.9" }, { name = "jsonref", specifier = ">=1.1.0" }, { name = "langchain-core", marker = "extra == 'langchain'", specifier = ">=0.3.64" }, { name = "mcp", specifier = ">=1.10.1" },