mcp-agent gives you two complementary ways to expose agent behaviour:
Decorator-based tools – mark a plain Python function with @app.tool or @app.async_tool to expose it as an MCP tool. This is the quickest way to add synchronous or long-running behaviour to your app.
Workflow classes – build stateful, structured flows by subclassing Workflow[T]. Workflows give you fine-grained control over orchestration, retries, and Temporal integration.
Both options register MCP tools automatically, so any MCP client can invoke them. The high-level “workflow patterns” in examples/workflows (parallel, router, orchestrator, etc.) are built using these same primitives—they are patterns, not the Workflow base class itself.The rest of this page walks through the decorators first (because most apps start there) and then dives into the Workflow class.
Agents often need to run tasks that take longer than an MCP request allows (multi-step research, human-in-the-loop flows, durable Temporal runs). Decorate those entry points with @app.async_tool:
@app.async_tool starts a workflow in the background and returns identifiers that clients can poll via the built-in workflows-get_status tool. This pattern keeps your agent responsive even when the underlying work takes minutes or requires human decisions.
Tip: Agent servers rely heavily on these decorators—see Agent Servers for end-to-end examples.
The Workflow[T] base class lets you model multi-step or stateful logic while still exposing an MCP tool. Workflows are most useful when you need retries, shared state, or tight integration with the execution engine (asyncio or Temporal).
The repository has an examples/workflows directory that demonstrates higher-level agent patterns: router, parallel fan-out, orchestrator, evaluator/optimizer, and more. These samples compose agents and AugmentedLLMs with helpers from mcp_agent.workflows.factory. They do not correspond one-to-one with the Workflow base class above—they are ready-made orchestration patterns you can adopt or customise.Use the patterns when you want opinionated orchestration, and drop down to the Workflow class (or @app.async_tool) when you need bespoke control flow.
Workflows seamlessly support Temporal for durable execution:
# Configure for Temporalapp = MCPApp( name="temporal_agent", settings=Settings( execution_engine="temporal", temporal=TemporalSettings( host="localhost", port=7233, namespace="default", task_queue="mcp-agent" ) ))@app.workflowclass DurableWorkflow(Workflow[str]): @app.workflow_run async def run(self, task: str) -> WorkflowResult[str]: # This workflow is now durable # It can be paused, resumed, and retried # Wait for signal (human-in-the-loop) await app.context.executor.signal_bus.wait_for_signal( Signal(name="approve", workflow_id=self.id) ) # Continue after approval result = await self.process_with_approval(task) return WorkflowResult(value=result)
Use @app.async_tool for long-running operations that need polling
Use Workflow class for complex, multi-step processes
Type Hints and Documentation
Always provide type hints and docstrings:
@app.toolasync def process_data( data: dict, options: Optional[dict] = None) -> dict: """ Process data with optional transformations. Args: data: Input data to process options: Optional processing options Returns: Processed data dictionary """ # Implementation