Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions examples/workflows/workflow_orchestrator_worker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,14 @@

app = MCPApp(name="assignment_grader_orchestrator")


async def example_usage():
@app.tool
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=""
Comment on lines +92 to +99
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

Confirm async support for @app.tool and avoid nested app.run if unnecessary

The decorator is documented as declaring a synchronous MCP tool; using it on an async def that itself calls async with app.run() risks double-running the app context or returning an un-awaited coroutine, depending on adapter behavior. Please verify that MCPApp.tool fully supports async functions and that nesting app.run() inside a tool is intended.

Run this to audit existing usage patterns and catch potential double-run issues:

If async is not supported, either:

  • Convert to a sync tool and delegate to an internal async helper, or
  • Keep the function async but register it as a workflow/tool mode that awaits it without wrapping app.run() again.

🏁 Script executed:

#!/bin/bash
# 1) See how @app.tool is used elsewhere (sync vs async)
rg -nP -C2 '@app\.tool\b' --glob '!**/site-packages/**'

# 2) Find functions that call app.run() inside a decorated tool
rg -nP -C3 '@app\.tool\b.*\n\s*(async\s+def|def)\s+[a-zA-Z_]\w*\(.*\):\n(?s).*?app\.run\('

# 3) Inspect the tool adapter to confirm behavior
rg -nP -C5 'def\s+tool\(' src/mcp_agent/app.py

Length of output: 511


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "Top-level files:"
ls -la | sed -n '1,120p' || true
echo
echo "1) Search for @app.tool usages (with context)"
rg -n --hidden -S -C3 '@app\.tool\b' || true
echo
echo "2) Search for app.run( usages (with context)"
rg -n --hidden -S -C3 'app\.run\(' || true
echo
echo "3) Print src/mcp_agent/app.py (first 400 lines) to inspect `tool` implementation"
if [ -f src/mcp_agent/app.py ]; then
  sed -n '1,400p' src/mcp_agent/app.py || true
else
  echo "src/mcp_agent/app.py not found"
fi

Length of output: 95284


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/mcp_agent/app.py (lines 700-820) ----"
sed -n '700,820p' src/mcp_agent/app.py || echo "src/mcp_agent/app.py: EOF or not found"

echo
echo "---- src/mcp_agent/server/app_server.py (lines 940-1000) ----"
sed -n '940,1000p' src/mcp_agent/server/app_server.py || echo "src/mcp_agent/server/app_server.py: EOF or not found"

echo
echo "---- src/mcp_agent/server/tool_adapter.py (full file) ----"
sed -n '1,400p' src/mcp_agent/server/tool_adapter.py || echo "src/mcp_agent/server/tool_adapter.py: EOF or not found"

Length of output: 11872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/mcp_agent/app.py (lines 360-520) ----"
sed -n '360,520p' src/mcp_agent/app.py || echo "src/mcp_agent/app.py: EOF or not found"

echo
echo "---- src/mcp_agent/server/app_server.py (lines 1000-1400) ----"
sed -n '1000,1400p' src/mcp_agent/server/app_server.py || echo "src/mcp_agent/server/app_server.py: EOF or not found"

Length of output: 22181


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for _resolve_workflows_and_context and _get_attached_app definitions..."
rg -n --hidden -S "_resolve_workflows_and_context" || true
rg -n --hidden -S "_get_attached_app" || true

echo
echo "If found, print surrounding lines from src/mcp_agent/server/app_server.py"
rg -n --hidden -S "_resolve_workflows_and_context|_get_attached_app" src/mcp_agent/server/app_server.py -n -C5 || true

echo
echo "Print src/mcp_agent/server/app_server.py around likely helper definitions (lines 1-420)"
sed -n '1,420p' src/mcp_agent/server/app_server.py || true

Length of output: 23098


Remove nested async with app.run() from @app.tool functions — app.tool supports async but app.run() will tear down a server-managed app context

@app.tool is intended to wrap an async function into an auto-generated Workflow (docs/examples and src/mcp_agent/app.py show async @app.tool usage). However MCPApp.run always calls cleanup() on exit (src/mcp_agent/app.py: run), and create_declared_function_tools in src/mcp_agent/server/app_server.py starts the workflow and supplies the app/context for server-exposed tools—so calling async with app.run() inside a function that will be registered as a tool will likely tear down the shared app/context after the tool returns.

  • Action: Remove the nested async with app.run() in examples/workflows/workflow_orchestrator_worker/main.py (≈lines 92–103); instead use the workflow-provided context (accept an app_ctx/app context or use the context passed by the engine).
  • If the function is only meant to be run as a standalone script, keep app.run() but do not expose it as an MCP tool; if you need asynchronous start/status endpoints, use @app.async_tool or a Workflow class.
🤖 Prompt for AI Agents
In examples/workflows/workflow_orchestrator_worker/main.py around lines 92 to
99, remove the nested "async with app.run()" call inside the @app.tool async
function because tearing down a server-managed app context from within a
declared tool will call cleanup() and break the shared app; instead have the
tool use the workflow-provided app/context (accept an app_ctx or app parameter
or use the provided engine/context passed into the workflow) to access
resources, or convert this function to a standalone script that calls app.run()
but is not exposed as an MCP tool; update the function signature and internal
usage to rely on the injected context rather than starting/stopping the app.

async with app.run() as orchestrator_app:
logger = orchestrator_app.logger

Expand Down Expand Up @@ -177,6 +183,7 @@ async def example_usage():
summary = await orchestrator_app.get_token_summary()
print(f"\nTotal Cost: ${summary.cost:.4f}")
print("=" * 60)
return result


def display_node_tree(
Expand Down
Loading