Skip to content
22 changes: 14 additions & 8 deletions examples/workflows/workflow_intent_classifier/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@

app = MCPApp(name="intent_classifier")


@app.tool
async def example_usage()->str:
'''
async def example_usage() -> str:
"""
this is an example function/tool call that uses the intent classification workflow.
It uses both the OpenAI embedding intent classifier and the OpenAI LLM intent classifier
'''
results=""
"""

results = ""

async with app.run() as intent_app:
logger = intent_app.logger
context = intent_app.context
Expand Down Expand Up @@ -48,7 +49,9 @@ async def example_usage()->str:
)

logger.info("Embedding-based Intent classification results:", data=output)
results="Embedding-based Intent classification results: " + ", ".join(r.intent for r in output)
results = "Embedding-based Intent classification results: " + ", ".join(
r.intent for r in output
)

llm_intent_classifier = OpenAILLMIntentClassifier(
intents=[
Expand All @@ -72,10 +75,13 @@ async def example_usage()->str:
)

logger.info("LLM-based Intent classification results:", data=output)
results+="LLM-based Intent classification results: " + ", ".join(r.intent for r in output)
results += "LLM-based Intent classification results: " + ", ".join(
r.intent for r in output
)

return results


if __name__ == "__main__":
import time

Expand Down
9 changes: 5 additions & 4 deletions examples/workflows/workflow_orchestrator_worker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,15 @@

app = MCPApp(name="assignment_grader_orchestrator")


@app.tool
async def example_usage()->str:
'''
async def example_usage() -> str:
"""
this example function/tool call will use an orchestrator workflow
to dynamically plan and execute across a number of agents to grade
a short story.
'''
result=""
"""
result = ""
async with app.run() as orchestrator_app:
logger = orchestrator_app.logger

Expand Down
2 changes: 1 addition & 1 deletion src/mcp_agent/cli/cloud/commands/app/status/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ async def print_mcp_server_details(server_url: str, api_key: str) -> None:

except Exception as e:
raise CLIError(
f"Error connecting to MCP server at {server_url}: {str(e)}"
f"Error obtaining details from MCP server at {server_url}: {str(e)}"
) from e


Expand Down
137 changes: 12 additions & 125 deletions src/mcp_agent/cli/cloud/commands/app/workflows/main.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import json
import textwrap
from typing import Optional
from datetime import datetime

import typer
from rich.console import Group
from rich.panel import Panel
from rich.prompt import Prompt
Comment on lines 4 to 5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Remove duplicate “no runs” panel; defer to shared utility for consistent output.

This block reintroduces bespoke rendering and diverges from print_workflow_runs() behavior noted elsewhere in the PR.

-from rich.panel import Panel
@@
-        if not res.workflow_runs:
-            console.print(
-                Panel(
-                    "[yellow]No workflow runs found[/yellow]",
-                    title="Workflow Runs",
-                    border_style="blue",
-                )
-            )
-            return
+        if not res.workflow_runs:
+            print_workflow_runs([])
+            return

Also applies to: 156-165


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- search: occurrences of print_workflow_runs ----"
rg -n --hidden -S "print_workflow_runs" -g '!*node_modules/*' || true

echo
echo "---- search: definition of print_workflow_runs ----"
rg -n --hidden -S "def print_workflow_runs" -g '!*node_modules/*' || true

echo
echo "---- search: 'No workflow runs found' literal ----"
rg -n --hidden -S "No workflow runs found" -g '!*node_modules/*' || true

echo
echo "---- search: Panel( usage ----"
rg -n --hidden -S "Panel\\(" -g '!*node_modules/*' || true

echo
FILE="src/mcp_agent/cli/cloud/commands/app/workflows/main.py"
if [ -f "$FILE" ]; then
  echo "---- head of $FILE ----"
  sed -n '1,120p' "$FILE" || true

  echo
  echo "---- lines ~140-180 of $FILE ----"
  sed -n '140,180p' "$FILE" || true
else
  echo "File not found: $FILE"
fi

Length of output: 6215


Replace local "No workflow runs" Panel with print_workflow_runs([]) and remove Panel import.

Use the shared print_workflow_runs for consistent output; drop the Panel import in this file.

-from rich.panel import Panel
@@
-        if not res.workflow_runs:
-            console.print(
-                Panel(
-                    "[yellow]No workflow runs found[/yellow]",
-                    title="Workflow Runs",
-                    border_style="blue",
-                )
-            )
-            return
+        if not res.workflow_runs:
+            print_workflow_runs([])
+            return

[src/mcp_agent/cli/cloud/commands/app/workflows/main.py — import lines and print_runs_list block]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from rich.panel import Panel
from rich.prompt import Prompt
from rich.prompt import Prompt
if not res.workflow_runs:
print_workflow_runs([])
return
🤖 Prompt for AI Agents
In src/mcp_agent/cli/cloud/commands/app/workflows/main.py around lines 4-5,
remove the unused Panel import and replace the local Panel("No workflow runs",
...) usage with a call to the shared print_workflow_runs([]) helper for
consistent output; if print_workflow_runs is not already imported, add the
appropriate import from the shared printing module, then delete the Panel import
line.

from rich.syntax import Syntax
from rich.text import Text

from mcp_agent.cli.auth import load_api_key_credentials
from mcp_agent.cli.cloud.commands.workflows.utils import (
print_workflows,
print_workflow_runs,
)
from mcp_agent.cli.config import settings
from mcp_agent.cli.core.api_client import UnauthenticatedError
from mcp_agent.cli.core.constants import (
Expand Down Expand Up @@ -133,86 +131,17 @@ async def print_mcp_server_workflow_details(server_url: str, api_key: str) -> No

except Exception as e:
raise CLIError(
f"Error connecting to MCP server at {server_url}: {str(e)}"
f"Error getting workflow details from MCP server at {server_url}: {str(e)}"
) from e


# FastTool includes 'self' in the run parameters schema, so remove it for clarity
def clean_run_parameters(schema: dict) -> dict:
schema = schema.copy()

if "properties" in schema and "self" in schema["properties"]:
schema["properties"].pop("self")

if "required" in schema and "self" in schema["required"]:
schema["required"] = [r for r in schema["required"] if r != "self"]

return schema


async def print_workflows_list(session: MCPClientSession) -> None:
"""Prints the available workflow types for the server."""
try:
with console.status("[bold green]Fetching server workflows...", spinner="dots"):
res = await session.list_workflows()

if not res.workflows:
console.print(
Panel(
"[yellow]No workflows found[/yellow]",
title="Workflows",
border_style="blue",
)
)
return

panels = []

for workflow in res.workflows:
header = Text(workflow.name, style="bold cyan")
desc = textwrap.dedent(
workflow.description or "No description available"
).strip()
body_parts: list = [Text(desc, style="white")]

# Capabilities
capabilities = getattr(workflow, "capabilities", [])
cap_text = Text("\nCapabilities:\n", style="bold green")
cap_text.append_text(Text(", ".join(capabilities) or "None", style="white"))
body_parts.append(cap_text)

# Tool Endpoints
tool_endpoints = getattr(workflow, "tool_endpoints", [])
endpoints_text = Text("\nTool Endpoints:\n", style="bold green")
endpoints_text.append_text(
Text("\n".join(tool_endpoints) or "None", style="white")
)
body_parts.append(endpoints_text)

# Run Parameters
if workflow.run_parameters:
run_params = clean_run_parameters(workflow.run_parameters)
properties = run_params.get("properties", {})
if len(properties) > 0:
schema_str = json.dumps(run_params, indent=2)
schema_syntax = Syntax(
schema_str, "json", theme="monokai", word_wrap=True
)
body_parts.append(Text("\nRun Parameters:", style="bold magenta"))
body_parts.append(schema_syntax)

body = Group(*body_parts)

panels.append(
Panel(
body,
title=header,
border_style="green",
expand=False,
)
)

console.print(Panel(Group(*panels), title="Workflows", border_style="blue"))
print_workflows(res.workflows if res and res.workflows else [])

except Exception as e:
print_error(f"Error fetching workflows: {str(e)}")
Expand All @@ -236,7 +165,11 @@ async def print_runs_list(session: MCPClientSession) -> None:

def get_start_time(run: WorkflowRun):
try:
return run.temporal.start_time if run.temporal else 0
return (
run.temporal.start_time
if run.temporal and run.temporal.start_time is not None
else 0
)
except AttributeError:
return 0

Expand All @@ -246,53 +179,7 @@ def get_start_time(run: WorkflowRun):
reverse=True,
)

console.print(f"\n[bold blue] Workflow Runs ({len(sorted_runs)})[/bold blue]")

for i, run in enumerate(sorted_runs):
if i > 0:
console.print()

start = getattr(run.temporal, "start_time", None)
start_str = (
datetime.fromtimestamp(start).strftime("%Y-%m-%d %H:%M:%S")
if start
else "N/A"
)

end = getattr(run.temporal, "close_time", None)
end_str = (
datetime.fromtimestamp(end).strftime("%Y-%m-%d %H:%M:%S")
if end
else "N/A"
)

status = run.status.lower()
if status == "completed":
status_text = f"[green]🟢 {status}[/green]"
elif status == "error" or status == "failed":
status_text = f"[red]🔴 {status}[/red]"
elif status == "running":
status_text = f"[yellow]🔄 {status}[/yellow]"
else:
status_text = f"❓ {status}"

console.print(
f"[bold cyan]{run.name or 'Unnamed Workflow'}[/bold cyan] {status_text}"
)
console.print(f" Run ID: {run.id}")

if run.temporal and run.temporal.workflow_id:
console.print(f" Workflow ID: {run.temporal.workflow_id}")

console.print(f" Started: {start_str}")
if end_str != "N/A":
console.print(f" Completed: {end_str}")

# Show execution time if available
if hasattr(run.temporal, "execution_time") and run.temporal.execution_time:
duration = end - start if (start and end) else None
if duration:
console.print(f" Duration: {duration:.2f}s")
print_workflow_runs(sorted_runs)

except Exception as e:
print_error(f"Error fetching workflow runs: {str(e)}")
2 changes: 1 addition & 1 deletion src/mcp_agent/cli/cloud/commands/logger/tail/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ def _display_text_log_entry(entry: Dict[str, Any]) -> None:
console.print(
f"[bright_black not bold]{timestamp}[/bright_black not bold] "
f"[{level_style}]{level:7}[/{level_style}] ",
message_text
message_text,
)


Expand Down
50 changes: 18 additions & 32 deletions src/mcp_agent/cli/cloud/commands/workflows/cancel/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

import typer

from mcp_agent.app import MCPApp
from mcp_agent.cli.auth.main import load_api_key_credentials
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 mcp_agent.cli.mcp_app.mcp_client import mcp_connection_session
from mcp_agent.cli.utils.ux import console, print_error
from ...utils import (
setup_authenticated_client,
handle_server_api_errors,
Expand Down Expand Up @@ -37,35 +36,20 @@ async def _cancel_workflow_async(
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)
effective_api_key = load_api_key_credentials()

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",
)
if not effective_api_key:
raise CLIError("Must be logged in to access server. Run 'mcp-agent login'.")

Comment on lines +39 to 43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Also honor env-configured API keys to avoid false “not logged in” errors

This only reads the stored credentials file. Align with other commands by also checking settings.API_KEY (env-backed).

Apply this diff in-place:

-    effective_api_key = load_api_key_credentials()
+    # Prefer env/configured API key, fall back to stored credentials
+    from mcp_agent.cli.config import settings
+    effective_api_key = settings.API_KEY or load_api_key_credentials()

And ensure the import exists at the top (outside this range):

+from mcp_agent.cli.config import settings
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
effective_api_key = load_api_key_credentials()
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",
)
if not effective_api_key:
raise CLIError("Must be logged in to access server. Run 'mcp-agent login'.")
# Prefer env/configured API key, fall back to stored credentials
from mcp_agent.cli.config import settings
effective_api_key = settings.API_KEY or load_api_key_credentials()
if not effective_api_key:
raise CLIError("Must be logged in to access server. Run 'mcp-agent login'.")
Suggested change
effective_api_key = load_api_key_credentials()
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",
)
if not effective_api_key:
raise CLIError("Must be logged in to access server. Run 'mcp-agent login'.")
from mcp_agent.cli.config import settings
🤖 Prompt for AI Agents
In src/mcp_agent/cli/cloud/commands/workflows/cancel/main.py around lines 39 to
43, the code only checks stored credentials and raises a CLIError when none are
found; update it to also honor env-configured keys by using settings.API_KEY as
a fallback (i.e., if load_api_key_credentials() returns falsy, use
settings.API_KEY before raising), and ensure settings is imported at the top of
the file.

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"
try:
async with mcp_connection_session(
server_url, effective_api_key
) as mcp_client_session:
try:
with console.status(
"[bold yellow]Cancelling workflow...", spinner="dots"
):
success = await mcp_client_session.cancel_workflow(run_id)

if success:
console.print()
Expand All @@ -74,7 +58,9 @@ async def _cancel_workflow_async(
if reason:
console.print(f" Reason: [dim]{reason}[/dim]")
else:
raise CLIError(f"Failed to cancel workflow with run ID {run_id}")
print_error(f"Failed to cancel workflow with run ID {run_id}")
except Exception as e:
print_error(f"Error cancelling workflow with run ID {run_id}: {str(e)}")

except Exception as e:
raise CLIError(
Expand Down
Loading
Loading