Skip to content

Conversation

@rholinshead
Copy link
Member

@rholinshead rholinshead commented Sep 11, 2025

Description

The deserialization of workflow runs is failing right now because it expects JSON but gets a serialized python structure, so fix to try deserializing python before trying json.

Also, clean up the presentation to be a basic list so that full IDs can be easily copied for use in subsequent commands (like getting run results)

Test

BEFORE (with fix):

                                                    Workflow Runs                                                     
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┓
┃          ┃                                                              ┃          ┃          ┃ Start   ┃          ┃
┃ Name     ┃ Workflow ID                                                  ┃ Run ID   ┃ Status   ┃ Time    ┃ End Time ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
│ Workflow │ WorkflowWithInteraction-70408426-f811-44a0-9bda-108b8b836016 │ 01993aed │ complete │ 2025-09 │ 2025-09- │
│ WithInte │                                                              │ -6b8c-7e │ d        │ -11     │ 11       │
│ raction  │                                                              │ b2-abb2- │          │ 18:37:4 │ 18:37:43 │
│          │                                                              │ 2c4ce0cb │          │ 2       │          │
│          │                                                              │ 370b     │          │         │          │
│          │                                                              │          │          │         │          │
│ Orchestr │ OrchestratorWorkflow-61679337-2680-48b0-8ccd-cb95f0a4fb39    │ 01993ae7 │ complete │ 2025-09 │ 2025-09- │
│ atorWork │                                                              │ -1c5c-7d │ d        │ -11     │ 11       │
│ flow     │                                                              │ 74-909d- │          │ 18:30:4 │ 18:33:19 │
│          │                                                              │ 89e68d33 │          │ 8       │          │
│          │                                                              │ 0b63     │          │         │          │
└──────────┴──────────────────────────────────────────────────────────────┴──────────┴──────────┴─────────┴──────────┘

NOW (fix + updated presentation):

uv run mcp-agent cloud app workflows --id=https://1119u5fsnun3acsez6j2sxi5turjr6dc.deployments.mcp-agent-cloud.lastmileai.dev
Initializing MCPClientSession
SUCCESS: Connected to MCP server at 
https://1119u5fsnun3acsez6j2sxi5turjr6dc.deployments.mcp-agent-cloud.lastmileai.dev using sse.

What would you like to display?
1: List Workflows
2: List Workflow Runs
0: List All

What would you like to display? (0): 2

 Workflow Runs (4)
WorkflowWithInteraction 🔄 running
  Run ID: 01993b2e-90d3-749b-8edf-9a260ede3107
  Workflow ID: WorkflowWithInteraction-80ecc05a-41a2-4d31-96e1-38aca8bda3b7
  Started: 2025-09-11 19:48:51

OrchestratorWorkflow 🔄 running
  Run ID: 01993b2e-1b4b-7ca5-9a24-7e6ccf805b75
  Workflow ID: OrchestratorWorkflow-975203aa-e2db-4295-8e28-359ec154447e
  Started: 2025-09-11 19:48:21

WorkflowWithInteraction 🟢 completed
  Run ID: 01993aed-6b8c-7eb2-abb2-2c4ce0cb370b
  Workflow ID: WorkflowWithInteraction-70408426-f811-44a0-9bda-108b8b836016
  Started: 2025-09-11 18:37:42
  Completed: 2025-09-11 18:37:43
  Duration: 1.68s

OrchestratorWorkflow 🟢 completed
  Run ID: 01993ae7-1c5c-7d74-909d-89e68d330b63
  Workflow ID: OrchestratorWorkflow-61679337-2680-48b0-8ccd-cb95f0a4fb39
  Started: 2025-09-11 18:30:48
  Completed: 2025-09-11 18:33:19
  Duration: 150.82s

Summary by CodeRabbit

  • New Features
    • Workflow runs view now shows richer status with icons, run/workflow IDs, start/end times, and duration.
    • Added a helper to display token usage summaries in examples.
  • Improvements
    • More robust parsing of workflow run results and workflows, with clearer errors on invalid data.
  • Bug Fixes
    • Example usage now correctly returns the accumulated result.
  • Style
    • Minor formatting and whitespace updates with no functional impact.

@coderabbitai
Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

Adds token summary display to basic agent example; normalizes and enriches workflow run/result structures and parsing in the MCP client; refactors CLI workflow runs output from table to per-run blocks with status icons and duration; standardizes formatting in examples and logger tail; updates example function signatures.

Changes

Cohort / File(s) Summary
Examples — basic agent
examples/basic/mcp_basic_agent/main.py
Added public function display_token_summary(app_ctx, agent=None); reformatted example_usage signature spacing; minor whitespace/docstring wrapping.
Examples — model selector
examples/basic/mcp_model_selector/main.py
Reformatted example_usage signature; standardized string concatenation spacing; added explicit return of accumulated result; minor whitespace.
CLI — workflows UI
src/mcp_agent/cli/cloud/commands/app/workflows/main.py
Replaced table rendering with per-run block output; added status icons/colors, run/workflow IDs, start/end times, and duration when available; removed Table/Padding imports; preserved empty-state panel.
CLI — logger tail
src/mcp_agent/cli/cloud/commands/logger/tail/main.py
Reformatted imports and whitespace; consistent quote style in hasattr check; no logic changes.
MCP client — workflow parsing
src/mcp_agent/cli/mcp_app/mcp_client.py
Added ast import; WorkflowRunResult gains kind:str and metadata: Optional[dict] default None; WorkflowRunTemporal gains execution_time: Optional[float]; list_workflow_runs now parses string results via ast.literal_eval then json.loads, raising ValueError on failure; improved list_workflows JSON parsing.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as CLI (app workflows)
  participant Client as MCPClient
  participant API as Cloud API

  User->>CLI: list workflow runs
  CLI->>Client: list_workflow_runs(server)
  Client->>API: GET /workflows/runs
  API-->>Client: JSON runs[]
  loop For each run
    alt result is string
      Client->>Client: ast.literal_eval(result)
      alt ast fails
        Client->>Client: json.loads(result)
        alt json fails
          Client->>Client: raise ValueError("Invalid workflow run result data")
        end
      end
    else result is object
      Client->>Client: use as-is
    end
    Client->>Client: attach kind, metadata?, execution_time?
  end
  Client-->>CLI: runs (normalized)
  loop Render each run
    CLI->>CLI: Print name/status icon, IDs, times, duration
  end
Loading
sequenceDiagram
  participant Dev as Example (basic agent)
  participant App as MCPApp
  participant Agent as Agent (optional)

  Dev->>App: get_token_summary()
  App-->>Dev: totals
  alt Agent provided
    Dev->>Agent: token usage?
    Agent-->>Dev: per-agent usage
  end
  Dev->>Dev: display_token_summary(...)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • saqadri
  • jtcorbett
  • petersonbill64

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary change — fixing the app workflows list-runs request/deserialization and updating its presentation — which matches the PR's modifications to the CLI workflows command and the client parsing logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Poem

I thump my paws on terminal ground,
Icons spin, green lights abound.
Workflows march in tidy rows,
Durations tick, the parsing grows.
Tokens tallied—hop, hooray!
This bunny ships another day. 🐇✨

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/app-workflows-command

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@@ -1,3 +1,4 @@
import ast
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this used for?

Copy link
Member Author

Choose a reason for hiding this comment

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

For the ast.literal_eval deserialization (stringified python case) below

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (9)
src/mcp_agent/cli/cloud/commands/logger/tail/main.py (2)

139-166: Avoid re-creating the authenticated client in _fetch_logs.
You already have client; pass it through to reuse credentials and avoid redundant setup.

Apply alongside the signature update below:

-            asyncio.run(
-                _fetch_logs(
+            asyncio.run(
+                _fetch_logs(
                     server=server,
                     since=since,
                     grep_pattern=grep,
                     limit=limit,
                     order_by=order_by,
                     asc=asc,
                     desc=desc,
                     format=format,
                     app_identifier=app_identifier,
+                    client=client,
                 )
             )

And update the helper (outside this hunk):

-async def _fetch_logs(
-    server: Union[MCPApp, MCPAppConfiguration],
+async def _fetch_logs(
+    server: Union[MCPApp, MCPAppConfiguration],
     since: Optional[str],
     grep_pattern: Optional[str],
     limit: int,
     order_by: Optional[str],
     asc: bool,
     desc: bool,
     format: str,
     app_identifier: str,
+    client,
 ) -> None:
@@
-    client = setup_authenticated_client()
+    # client provided by caller to avoid re-auth/extra setup

268-272: Message clarity nit.
“Server URL not available - server may not be deployed” is fine; consider “Server URL unavailable; the app may not be deployed.” for consistency with other messages.

examples/basic/mcp_model_selector/main.py (1)

35-35: Prefer f-strings and join to reduce repeated string concatenations.
Keeps the sample concise and faster under CPython.

Example refactor:

-    result += "Smartest OpenAI model: " + model.name
+    result += f"Smartest OpenAI model: {model.name}"
@@
-    result += "\nMost balanced OpenAI model: " + model.name
+    result += f"\nMost balanced OpenAI model: {model.name}"
@@
-    result += "\nFastest and cheapest OpenAI model: " + model.name
+    result += f"\nFastest and cheapest OpenAI model: {model.name}"
@@
-    result += "\nSmartest Anthropic model: " + model.name
+    result += f"\nSmartest Anthropic model: {model.name}"
@@
-    result += "\nCheapest Anthropic model: " + model.name
+    result += f"\nCheapest Anthropic model: {model.name}"
@@
-    result += "\nSelect fastest model between gpt-4o/mini/sonnet/haiku: " + model.name
+    result += f"\nSelect fastest model between gpt-4o/mini/sonnet/haiku: {model.name}"
@@
-    result += "\nMost balanced model between gpt-4o/mini/sonnet/haiku: " + model.name
+    result += f"\nMost balanced model between gpt-4o/mini/sonnet/haiku: {model.name}"
@@
-    result += "\nBest model with context window >100k tokens: " + model.name
+    result += f"\nBest model with context window >100k tokens: {model.name}"
@@
-    result += (
-        "\nBest model with 50k-150k context window and tool calling: " + model.name
-    )
+    result += (
+        f"\nBest model with 50k-150k context window and tool calling: {model.name}"
+    )
@@
-    result += (
-        "\nFastest model with both tool calling and structured outputs: " + model.name
-    )
+    result += (
+        f"\nFastest model with both tool calling and structured outputs: {model.name}"
+    )

Also applies to: 48-48, 61-61, 74-74, 87-87, 105-105, 123-123, 143-143, 198-200, 219-221

examples/basic/mcp_basic_agent/main.py (2)

53-56: Docstring grammar nit.
“uses both OpenAI, Anthropic” → “uses both OpenAI and Anthropic.”

-    The example uses both OpenAI, Anthropic, and simulates a multi-turn conversation.
+    The example uses both OpenAI and Anthropic, and simulates a multi-turn conversation.

118-156: Token summary helper — nice addition.
Clear, readable breakdown; works with or without agent supplied.

Optionally return the formatted string instead of printing (so callers can log or test), and add an optional file-like stream parameter defaulting to sys.stdout.

src/mcp_agent/cli/mcp_app/mcp_client.py (2)

66-68: Result.kind now required — verify server payloads or make it tolerant.
Some servers may not emit a kind; making this required can raise validation errors.

Option A (tolerant model):

-class WorkflowRunResult(BaseModel):
+class WorkflowRunResult(BaseModel):
@@
-    kind: str
+    kind: str | None = None

Option B (normalize during parsing): see the list_workflow_runs suggestion below to default kind to "text" when absent.


167-175: Workflows parsing assumes dict-of-workflows; support list/singleton too.
Some servers return a list; broaden parsing to avoid failures.

-                    workflow_data = json.loads(item.text)
-                    for value in workflow_data.values():
-                        workflows.append(
-                            Workflow(
-                                **value,
-                            )
-                        )
+                    payload = json.loads(item.text)
+                    if isinstance(payload, dict):
+                        iterable = payload.values()
+                    elif isinstance(payload, list):
+                        iterable = payload
+                    else:
+                        iterable = [payload]
+                    for value in iterable:
+                        workflows.append(Workflow(**value))
src/mcp_agent/cli/cloud/commands/app/workflows/main.py (2)

246-247: Minor header formatting nit.
Drop the leading space before “Workflow Runs”.

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

288-293: Use execution_time if provided; otherwise compute.
Currently you compute duration but only when execution_time exists, and you ignore its value. Make it consistent.

-            # 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")
+            # Show duration
+            duration = None
+            if run.temporal:
+                if getattr(run.temporal, "execution_time", None):
+                    duration = run.temporal.execution_time
+                elif start and end:
+                    duration = end - start
+            if duration is not None:
+                console.print(f"  Duration: {duration:.2f}s")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d1e9bf and b24cbb1.

📒 Files selected for processing (5)
  • examples/basic/mcp_basic_agent/main.py (2 hunks)
  • examples/basic/mcp_model_selector/main.py (13 hunks)
  • src/mcp_agent/cli/cloud/commands/app/workflows/main.py (2 hunks)
  • src/mcp_agent/cli/cloud/commands/logger/tail/main.py (4 hunks)
  • src/mcp_agent/cli/mcp_app/mcp_client.py (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-05T14:31:48.139Z
Learnt from: rholinshead
PR: lastmile-ai/mcp-agent#414
File: src/mcp_agent/logging/logger.py:18-19
Timestamp: 2025-09-05T14:31:48.139Z
Learning: In the mcp-agent logging module (src/mcp_agent/logging/logger.py), temporalio should be imported lazily with try/except ImportError to avoid making it a hard dependency. Use temporalio.workflow.in_workflow() instead of isinstance checks on internal classes like _WorkflowInstanceImpl.

Applied to files:

  • src/mcp_agent/cli/mcp_app/mcp_client.py
🧬 Code graph analysis (3)
src/mcp_agent/cli/cloud/commands/logger/tail/main.py (1)
src/mcp_agent/cli/cloud/commands/utils.py (2)
  • setup_authenticated_client (18-32)
  • resolve_server (51-70)
examples/basic/mcp_basic_agent/main.py (3)
src/mcp_agent/app.py (1)
  • tool (715-767)
examples/basic/mcp_model_selector/main.py (1)
  • example_usage (15-223)
examples/workflows/workflow_orchestrator_worker/main.py (1)
  • example_usage (93-179)
examples/basic/mcp_model_selector/main.py (1)
examples/basic/mcp_basic_agent/main.py (1)
  • example_usage (51-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: checks / test
🔇 Additional comments (10)
src/mcp_agent/cli/cloud/commands/logger/tail/main.py (2)

20-23: Import style change only — LGTM.
No semantic changes.


189-195: No-op change (quote style) — OK.
Functionally identical.

examples/basic/mcp_model_selector/main.py (2)

15-16: Function signature/return type normalization — good change.
Consistent with other examples and tool return types.


223-224: Explicit return added — good.
Prevents accidental None returns in the example.

examples/basic/mcp_basic_agent/main.py (1)

51-57: Signature/docstring tidy — looks good.
Minor doc tweak suggestion below.

src/mcp_agent/cli/mcp_app/mcp_client.py (3)

1-1: Import ast for safe literal parsing — good call.
Keeps deserialization safe vs eval.


72-72: metadata Optional=None — good compatibility change.
Prevents empty-dict defaults and better reflects “may be absent.”


106-108: Temporal.execution_time added — good for durations.
No issues.

src/mcp_agent/cli/cloud/commands/app/workflows/main.py (2)

268-275: Status styling/emoji — nice clarity boost.
Clear, accessible at-a-glance status.


276-283: Per-run details — good defaults and visibility.
Printing full Run ID and optional Workflow ID matches the PR goal of copyability.

Comment on lines +197 to +207
if "result" in run_data and isinstance(run_data["result"], str):
try:
# Could be stringified python dict instead of valid JSON
run_data["result"] = ast.literal_eval(run_data["result"])
except (ValueError, SyntaxError) as e:
try:
run_data["result"] = json.loads(run_data["result"])
except json.JSONDecodeError:
raise ValueError(
f"Invalid workflow run result data: {e}"
) from e
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix deserialization + normalize result shape for Pydantic.
Great fallback to literal_eval→json. Add a final normalization so result is always a dict with kind/value to avoid ValidationError when servers return plain strings.

                     run_data = json.loads(item.text)
                     if "result" in run_data and isinstance(run_data["result"], str):
                         try:
                             # Could be stringified python dict instead of valid JSON
                             run_data["result"] = ast.literal_eval(run_data["result"])
                         except (ValueError, SyntaxError) as e:
                             try:
                                 run_data["result"] = json.loads(run_data["result"])
                             except json.JSONDecodeError:
                                 raise ValueError(
                                     f"Invalid workflow run result data: {e}"
                                 ) from e
+                    # Normalize result for model compatibility
+                    if "result" in run_data:
+                        res = run_data["result"]
+                        if isinstance(res, str):
+                            run_data["result"] = {"kind": "text", "value": res}
+                        elif isinstance(res, dict):
+                            run_data["result"].setdefault("kind", "text")
@@
-                except json.JSONDecodeError as e:
-                    raise ValueError(f"Invalid workflow run data: {e}") from e
+                except json.JSONDecodeError as e:
+                    raise ValueError(f"Invalid workflow run data: {e}") from e

Also applies to: 210-210

@rholinshead rholinshead merged commit b1506e6 into main Sep 12, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants