Guide · AWS Step Functions
Step Functions Express Workflows for MCP Servers
Express workflows run tool orchestration chains under 5 minutes with at-least-once execution semantics — they are the right choice when an MCP server needs to sequence tool calls and return a result synchronously to the LLM within the request window. Standard workflows are the right choice for long-running agent jobs (hours to days) that require exactly-once semantics and queryable execution history. Three points that trip engineers switching between the two: Express workflows have no execution history API — all state is in CloudWatch Logs, and if you don't configure logging before deploy you lose every execution trace permanently; Express Sync invocation blocks the caller until the state machine completes, so the calling Lambda must have a timeout greater than the state machine's maximum execution time; pricing is fundamentally different — Express charges per state transition ($1 per million) versus Standard's per-execution-start ($0.025 per 1,000), making Express dramatically cheaper for high-throughput tool pipelines.
TL;DR
Use Express Sync (StartSyncExecution) for MCP tool chains that must complete within 5 minutes and need a synchronous result — the API blocks and returns the output in the HTTP response. Use Express Async (StartExecution with an Express state machine) for fire-and-forget pipelines where the result is written to S3 or DynamoDB. Use Standard for agent workflows that span hours or require audit-quality exactly-once execution history. Configure CloudWatch Logs on Express state machines before first deploy — there is no retroactive execution history.
Workflow type comparison
| Property | Express Sync | Express Async | Standard |
|---|---|---|---|
| Max duration | 5 minutes | 5 minutes | 1 year |
| Execution semantics | At-least-once | At-least-once | Exactly-once |
| Execution history API | No — CloudWatch Logs only | No — CloudWatch Logs only | Yes — 90-day retention |
| Invocation | Blocking HTTP response | Returns execution ARN immediately | Returns execution ARN immediately |
| Pricing | $1 per million state transitions | $1 per million state transitions | $0.025 per 1,000 executions |
| Rate limit (starts/sec) | 2,000 (burst) | 2,000 (burst) | 2,000 (burst) |
Express Sync — synchronous MCP tool chain
Express Sync is the natural fit for an MCP server that needs to sequence two or three tool calls and return the result to the LLM in the same request. The MCP server calls StartSyncExecution, which blocks until the state machine finishes or hits the 5-minute wall. The response body contains the full output of the last state:
// MCP tool handler — invokes a Step Functions Express state machine synchronously
import { SFNClient, StartSyncExecutionCommand } from "@aws-sdk/client-sfn";
const sfn = new SFNClient({ region: process.env.AWS_REGION });
async function runToolChain(input: Record<string, unknown>): Promise<unknown> {
const result = await sfn.send(new StartSyncExecutionCommand({
stateMachineArn: process.env.TOOL_CHAIN_ARN,
input: JSON.stringify(input),
}));
if (result.status === "FAILED") {
throw new Error(`State machine failed: ${result.error} — ${result.cause}`);
}
if (result.status === "TIMED_OUT") {
throw new Error("Tool chain exceeded 5-minute execution limit");
}
return JSON.parse(result.output ?? "{}");
}
The calling Lambda's Timeout must exceed the state machine's expected execution time. If Lambda times out before Step Functions returns, the state machine continues running but the caller receives a Lambda timeout error. Set the calling Lambda timeout to the state machine's expected p99 + 30 seconds margin.
Creating the Express state machine (CloudFormation / CDK)
Express state machines require StateMachineType: EXPRESS in the resource definition. Logging must be configured at creation — there is no execution history API to fall back on:
# CloudFormation excerpt
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineType: EXPRESS
RoleArn: !GetAtt StepFunctionsExecutionRole.Arn
LoggingConfiguration:
Level: ALL # ERROR | FATAL | ALL — use ALL in production for full traces
IncludeExecutionData: true # includes input/output at each state transition
Destinations:
- CloudWatchLogsLogGroup:
LogGroupArn: !GetAtt ToolChainLogGroup.Arn
DefinitionString: !Sub |
{
"Comment": "MCP tool chain",
"StartAt": "FetchContext",
"States": {
"FetchContext": {
"Type": "Task",
"Resource": "${FetchContextFunction.Arn}",
"Next": "RunTool"
},
"RunTool": {
"Type": "Task",
"Resource": "${RunToolFunction.Arn}",
"End": true
}
}
}
# Log group — retain 30 days to control cost at high execution rates
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /stepfunctions/mcp-tool-chain
RetentionInDays: 30
The execution role needs permission to write to the log group in addition to invoking Lambda:
# IAM policy for Express state machine execution role
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["lambda:InvokeFunction"],
"Resource": [
"arn:aws:lambda:us-east-1:ACCOUNT:function:FetchContextFunction",
"arn:aws:lambda:us-east-1:ACCOUNT:function:RunToolFunction"
]
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogDelivery",
"logs:GetLogDelivery",
"logs:UpdateLogDelivery",
"logs:DeleteLogDelivery",
"logs:ListLogDeliveries",
"logs:PutLogEvents",
"logs:PutResourcePolicy",
"logs:DescribeResourcePolicies",
"logs:DescribeLogGroups"
],
"Resource": "*"
}
]
}
The logging IAM permissions look broader than expected because Step Functions creates a CloudWatch Logs resource policy (not just writes to the log group) to establish the delivery channel. Without logs:PutResourcePolicy and logs:DescribeResourcePolicies, the state machine fails to start with a permissions error even though it can technically write log events.
When Express Async is the right choice
Express Async starts execution and immediately returns an execution ARN — the caller does not wait for completion. This is useful for MCP-triggered background pipelines where the tool call returns a job ID and a separate polling tool checks status:
// MCP "start-pipeline" tool — fires Express state machine and returns execution ARN
async function startPipeline(input: unknown): Promise<{ executionArn: string }> {
const result = await sfn.send(new StartExecutionCommand({
stateMachineArn: process.env.PIPELINE_ARN, // Express state machine
input: JSON.stringify(input),
name: `run-${Date.now()}`, // must be unique per execution per state machine
}));
return { executionArn: result.executionArn! };
}
// MCP "check-pipeline" tool — queries execution result from CloudWatch Logs
// Standard GetExecutionHistory does NOT work for Express — use Logs Insights:
// fields @timestamp, @message
// | filter executionArn = "arn:aws:states:..." and type = "ExecutionSucceeded"
// | limit 1
The inability to call GetExecutionHistory on an Express execution is the most common Express Async mistake. The execution ARN is real and can be passed back to the caller, but querying it via the Step Functions API returns ExecutionDoesNotExist. Execution state must be read from CloudWatch Logs Insights or written to DynamoDB by the state machine's final state.
Standard workflows — long-running agent jobs
Use Standard workflows for agent pipelines that span multiple LLM turns, require human-in-the-loop approval via the callback pattern, or must survive overnight without timing out. Standard executions are queryable via GetExecutionHistory for 90 days and have exactly-once semantics — a state will not execute twice even if Step Functions retries internally due to a transient failure:
aws stepfunctions get-execution-history \
--execution-arn arn:aws:states:us-east-1:ACCOUNT:execution:MyStandardSM:my-run \
--query "events[?type=='TaskSucceeded'].[timestamp,details.output]" \
--output table
Standard executions cost $0.025 per 1,000 starts. At 1 execution per agent session with 10,000 sessions/month, that is $0.25/month — effectively free. The per-state-transition pricing of Express ($1 per million) is cheaper only at very high start rates or with many states per execution. A 10-state pipeline run 500,000 times/month costs $5 on Express (5M transitions) but $12.50 on Standard (500K starts × $0.025/1,000).
Monitoring Express workflow health
Because Express executions have no queryable execution history, monitoring relies entirely on CloudWatch metrics and Logs. Key metrics to alarm on:
| Metric | Namespace | Alarm threshold |
|---|---|---|
ExecutionsFailed |
AWS/States |
Alarm > 0 — any Express execution failure |
ExecutionThrottled |
AWS/States |
Alarm > 0 — state machine hitting start rate limit |
ExecutionTime |
AWS/States |
Alarm p95 > 240,000ms — approaching 5-minute limit |
LambdaFunctionsFailed |
AWS/States |
Alarm > 0 — Lambda errors within state machine |
Dimension all metrics by StateMachineArn to isolate failures per state machine. Without dimensioning, a noisy Standard workflow can mask failures in a silent Express pipeline.
Monitor your Step Functions–powered MCP tool chains
A Step Functions Express workflow failure is invisible to the LLM — it receives a Lambda error, retries the tool call, and if all retries fail, the agent session fails silently. AliveMCP probes each MCP endpoint every 60 seconds and alerts the moment a tool becomes unreachable — before an agent session fails for your users.
Join the waitlist →