Guide · Lambda Powertools
AWS Lambda Powertools Batch for MCP Servers
AWS Lambda Powertools Batch provides partial batch failure handling for SQS, Kinesis Data Streams, and DynamoDB Streams event sources — ensuring that when one record in a batch fails, only that record returns to the queue for retry, not the entire batch. For MCP server architectures that queue tool call requests via SQS, this prevents a single malformed payload from blocking all other tool calls and causing unbounded retry storms. Three things teams get wrong: not configuring ReportBatchItemFailures in the Lambda event source mapping — without this, Lambda ignores the batchItemFailures response and either retries the entire batch (SQS) or reprocesses from the failed sequence number (Kinesis); swallowing exceptions inside the record handler instead of letting them propagate — Powertools marks a record as failed only when the handler raises an unhandled exception; using SQS FIFO queues with partial failures — FIFO preserves order, so a failed record blocks all records behind it in the group regardless of Powertools configuration.
TL;DR
Use BatchProcessor(EventType.SQS) with process_partial_response() and return its result directly from the handler. Enable FunctionResponseTypes: [ReportBatchItemFailures] on the event source mapping. Let exceptions propagate from the record handler — don't catch them inside the handler function.
Why partial failures matter for MCP servers
Without partial failure handling, Lambda's default SQS behavior is all-or-nothing: if any record in a batch raises an exception, the entire batch goes back to the queue and all records are retried — including the ones that succeeded. For an MCP server processing tool call requests:
- A batch of 10 tool call requests arrives. Request 7 has a malformed payload.
- Requests 1–6 execute successfully. Request 7 raises
ValidationError. - Without partial failures: Lambda retries all 10 requests — requests 1–6 are re-executed, potentially causing duplicate operations.
- With Powertools Batch: only request 7 returns to the DLQ for inspection. Requests 1–6 are deleted from the queue.
from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
from aws_lambda_powertools.utilities.typing import LambdaContext
processor = BatchProcessor(event_type=EventType.SQS)
def record_handler(record: SQSRecord) -> dict:
# Raise any exception to mark this record as failed
# Do NOT catch exceptions here — let them propagate
payload = json.loads(record.body)
tool_name = payload["toolName"]
tool_input = payload["input"]
# Validate — raises ValueError on bad input, marking record as failed
if not tool_name or not isinstance(tool_input, dict):
raise ValueError(f"Invalid tool call payload: {payload}")
# Execute — raises Exception on tool failure, marking record as failed
result = execute_mcp_tool(tool_name, tool_input)
store_result(payload.get("callbackId"), result)
return result
@logger.inject_lambda_context
def handler(event: dict, context: LambdaContext) -> dict:
return process_partial_response(
event=event,
record_handler=record_handler,
processor=processor,
context=context,
)
The return value from process_partial_response() is a dict with a batchItemFailures key. Return this directly from the Lambda handler — Lambda reads it and only re-enqueues the failed records.
Event source mapping configuration
Partial failure responses only work if the Lambda event source mapping is configured to read them. Without ReportBatchItemFailures, Lambda ignores the batchItemFailures key entirely.
# CloudFormation / SAM template
Resources:
McpToolWorker:
Type: AWS::Serverless::Function
Properties:
Events:
McpToolQueue:
Type: SQS
Properties:
Queue: !GetAtt McpToolQueue.Arn
BatchSize: 10
FunctionResponseTypes:
- ReportBatchItemFailures # REQUIRED for partial failures
MaximumBatchingWindowInSeconds: 5
# CDK (TypeScript)
const queue = new sqs.Queue(this, "McpToolQueue", {
visibilityTimeout: Duration.seconds(30),
deadLetterQueue: { queue: dlq, maxReceiveCount: 3 },
});
fn.addEventSource(new SqsEventSource(queue, {
batchSize: 10,
reportBatchItemFailures: true, // REQUIRED
maxBatchingWindow: Duration.seconds(5),
}));
Kinesis Data Streams and DynamoDB Streams
Powertools Batch supports Kinesis and DynamoDB Streams with the same API, just different EventType values and record types:
from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response
from aws_lambda_powertools.utilities.data_classes.kinesis_stream_event import KinesisStreamRecord
from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import DynamoDBRecord
# Kinesis — item identifier is kinesis.sequenceNumber
kinesis_processor = BatchProcessor(event_type=EventType.KinesisDataStreams)
def kinesis_handler(record: KinesisStreamRecord) -> None:
data = base64.b64decode(record.kinesis.data).decode("utf-8")
event = json.loads(data)
process_mcp_event(event)
# DynamoDB Streams — item identifier is dynamodb.sequenceNumber
dynamo_processor = BatchProcessor(event_type=EventType.DynamoDBStreams)
def dynamo_handler(record: DynamoDBRecord) -> None:
if record.event_name == "INSERT":
new_item = record.dynamodb.new_image
sync_to_mcp_tool_registry(new_item)
Kinesis ordering caveat: Kinesis Data Streams processes shards in order. A partial failure response tells Lambda to retry from the first failed sequence number — records after the failed one are not processed until the failed record succeeds or expires. Unlike SQS, successful records after a failure in the same shard are not re-delivered, but they are blocked until the failure clears. Use a dead-letter queue with BisectBatchOnFunctionError: true to split batches in half and isolate poison pills faster.
Async batch processing
For MCP server handlers that make async I/O calls (external APIs, async database clients), use AsyncBatchProcessor:
from aws_lambda_powertools.utilities.batch import AsyncBatchProcessor, EventType, async_process_partial_response
async_processor = AsyncBatchProcessor(event_type=EventType.SQS)
async def async_record_handler(record: SQSRecord) -> dict:
payload = json.loads(record.body)
# Await external calls within the handler
async with aiohttp.ClientSession() as session:
result = await call_mcp_backend(session, payload)
return result
def handler(event: dict, context: LambdaContext) -> dict:
# async_process_partial_response runs handlers concurrently
# all records in the batch execute in parallel
return asyncio.run(
async_process_partial_response(
event=event,
record_handler=async_record_handler,
processor=async_processor,
context=context,
)
)
Async processing runs all record handlers concurrently within the batch. For MCP servers where each tool call makes a blocking external API request, this can reduce batch processing time from 10× record_latency to 1× record_latency (all run in parallel).
SQS FIFO limitations
SQS FIFO queues enforce ordering within a message group. Partial failure handling and FIFO ordering conflict: if record 7 in group "user-123" fails, records 8–10 in the same group cannot be delivered until record 7 is resolved (success or DLQ). Powertools Batch works correctly with FIFO queues — it returns the correct batchItemFailures — but the ordering guarantee means downstream records in the same group are delayed, not just re-routed.
For MCP tool call queuing where strict ordering within a session is not required, use standard SQS queues. Reserve FIFO queues only for cases where tool calls must execute in the exact sequence they were submitted (e.g., stateful session operations that must not interleave).
Common failures
| Symptom | Root cause | Fix |
|---|---|---|
| All records retried even when only one fails | ReportBatchItemFailures not set on event source mapping |
Add FunctionResponseTypes: [ReportBatchItemFailures] to the SQS event source in CloudFormation/CDK |
| Successful records re-executed on retry | Same as above — Lambda is treating the entire batch as failed | Verify ReportBatchItemFailures is configured; check Lambda event source mapping in AWS console |
| Failed records disappear silently without retry | Record handler catches exception and returns normally — Powertools marks it as success | Remove try/except blocks from record handler that swallow exceptions; let failures propagate |
BatchProcessingError raised at end of invocation |
All records in batch failed — Powertools raises this to prevent silent batch discard | Investigate root cause of record failures; if expected, catch BatchProcessingError in handler and return partial response explicitly |
| Kinesis records processed out of order after failure | Partial failure with Kinesis retries from failed sequence — expected behavior | Design handlers to be idempotent; use Powertools Idempotency to prevent duplicate processing |
Monitor MCP server endpoint availability
Batch failures mean your MCP tools are processing queued work. AliveMCP monitors the front door — continuous probes against your MCP server endpoint so you know before users notice if it stops responding.
Join the waitlist →