Skip to main content
mcp-agent can execute workflows on the built-in asyncio executor or on Temporal. Temporal adds durable state, automatic retries, and first-class pause/resume semantics for long-running MCP tools. The best part: switching is just a config change—set execution_engine: temporal and your existing workflows, tools, and agents keep working.
Outside of configuration (and starting a Temporal worker), you rarely need to touch your code. The same @app.workflow, @app.workflow_run, @app.async_tool, Agent, and AugmentedLLM APIs behave identically with Temporal behind the scenes.

When to choose Temporal

Temporal also unlocks adaptive throttling, workflow versioning, and seamless integration with mcp-agent Cloud.

Enable the Temporal engine

Switch the execution engine and point at a Temporal cluster (the examples assume temporal server start-dev):
Start a local server for development:
The configuration reference documents TLS, API keys, automatic retries, and metadata headers when you deploy to production. Temporal relies on a replay model: the deterministic parts of your workflow (the code you wrote under @app.workflow_run) are re-executed after a crash, while non-deterministic work—LLM calls, MCP tool calls, HTTP requests—is automatically offloaded to Temporal activities by the executor. mcp-agent handles that split for you; you keep writing straightforward async Python.

Run a worker

Workers poll Temporal for workflow/activity tasks. The helper create_temporal_worker_for_app wires your MCPApp into a worker loop:
Keep this process running while you start workflows or expose durable tools.

Launch workflows (or tools) durably

The executor API is unchanged—Temporal persists the state machine behind the scenes:
You can also expose a Temporal run as an MCP tool. The orchestrator example uses @app.async_tool so clients invoke a single tool call while Temporal handles retries and state:
This pattern is ideal for “long-running tool” buttons in MCP clients: the tool call returns immediately with a run identifier and you can stream progress or resume later.

Human approvals, pause, and resume

Temporal signals map directly to executor.wait_for_signal and executor.signal_workflow. The pause/resume workflow shipped in examples/mcp_agent_server/temporal demonstrates the flow:
Resume it from another process, the Temporal UI, or mcp-agent Cloud (mcp-agent workflows resume):
The same helper works on the asyncio executor via app.context.executor.signal_bus, so you can prototype locally and switch to Temporal when you need durability.

Nested tools and elicitation

The Temporal server example also shows how durable workflows call nested MCP servers and trigger MCP elicitation when a human response is required. Activities such as call_nested_elicitation log progress via app.app.logger so the request trace and Temporal history stay aligned.

Configure workflow-task modules and retry policies

Add optional top-level overrides to preload custom workflow tasks and refine retry behaviour:
  • workflow_task_modules entries are standard Python import paths; they are imported before the worker begins polling so @workflow_task functions register globally.
  • workflow_task_retry_policies accepts exact activity names, module or class suffixes (prefix.suffix), trailing wildcards like custom_tasks.*, or the global *. The most specific match wins.
  • Retry intervals accept seconds (1.5), strings ("2"), or timedelta objects.
  • Marking error types in non_retryable_error_types prevents Temporal from re-running an activity when the failure is not recoverable (see the Temporal failure reference). For provider SDKs, useful values include:
    • OpenAI/Azure OpenAI: AuthenticationError, PermissionDeniedError, BadRequestError, NotFoundError, UnprocessableEntityError.
    • Anthropic: AuthenticationError, PermissionDeniedError, BadRequestError, NotFoundError, UnprocessableEntityError.
    • Azure AI Inference: HttpResponseError (raised with non-retryable status codes such as 400/401/403/404/422).
    • Google GenAI: InvalidArgument, FailedPrecondition, PermissionDenied, NotFound, Unauthenticated.
  • mcp-agent raises WorkflowApplicationError (wrapping Temporal’s ApplicationError when available) for known non-retryable provider failures, so these policies work even if you run without the Temporal extra installed.
  • Inspect an activity’s fully-qualified name via func.execution_metadata["activity_name"] or through the Temporal UI history when adding a mapping.
  • Temporal matches non_retryable_error_types using the exception class name string you supply (see the RetryPolicy reference). Use the narrowest names possible—overly generic entries such as NotFoundError can suppress legitimate retries if a workflow expects to handle that condition and try again.
With these pieces in place you can gradually introduce durability: start on asyncio, flip the config once you need retries/pause/resume, then iterate on policies and module preloading as your workflow surface grows.

Operating durable agents

  • Temporal Web UI (http://localhost:8233) lets you inspect history, replay workflow code, and emit signals.
  • Workflow handles expose describe(), query(), and list() helpers for custom dashboards or integrations.
  • Observability: enable OpenTelemetry (otel.enabled: true) to stream spans + logs while Temporal provides event history.
  • Deployment: mcp-agent Cloud uses the same configuration. Once deployed, Cloud exposes CLI commands (mcp-agent workflows list, resume, cancel) that call the same signal/query APIs shown above.

Deeper dives

  • Temporal example suite – side-by-side asyncio vs. Temporal workflows (basic, router, parallel, evaluator-optimizer) plus a detailed README walking through setup.
  • Temporal MCP server – exposes durable workflows as MCP tools, demonstrates workflows-resume, and includes a client script for pause/resume flows.
  • Temporal tracing example – shows the same code running with Jaeger exports once you flip the execution_engine.

Example projects