Guide · ECS Fargate · Lambda · Service Quotas · Scaling
ECS Fargate and Lambda Quota Management for MCP Servers
MCP servers can be deployed as long-running ECS Fargate services or as serverless Lambda functions behind an HTTP adapter. Both deployment models have distinct quota profiles that constrain horizontal scaling. ECS Fargate deployments run into task count quotas — the number of running Fargate tasks per cluster has a default ceiling of 500, which sounds large until you are running multi-tenant deployments with one task per customer. Lambda-based MCP tool handlers share the account-wide concurrent executions quota across every Lambda function in every service in the account. This guide covers the specific quotas, their monitoring approaches, and the increase request strategies for each deployment model.
TL;DR
For ECS Fargate: monitor running task count with Container Insights (ECS/ContainerInsights RunningTaskCount) and alarm at 80% of the per-cluster Fargate task quota (default 500). For Lambda: monitor AWS/Usage ResourceCount Executions against the concurrent executions quota (default 1,000); use reserved concurrency on critical MCP tool handler functions to protect them from account-wide throttles. For both: use aws service-quotas request-service-quota-increase proactively at 60% utilization rather than waiting for 80% alarms to fire before submitting requests.
ECS Fargate quota breakdown
ECS Fargate has several interlocking quotas. Hitting any one of them causes task scheduling to fail silently — the ECS service reports PROVISIONING state indefinitely without a clear error message in the console.
| Quota name | Default | Quota code | Impact when exhausted |
|---|---|---|---|
| Running Fargate tasks per cluster | 500 | L-3794C6DC | No new tasks can be scheduled in the cluster; existing tasks run normally |
| Running Fargate tasks per region | 1,000 | L-DC8A8B7C | No new tasks can be scheduled in any cluster in the region |
| ECS services per cluster | 500 | L-B7F81543 | Cannot create new ECS services — affects multi-tenant service-per-customer deployments |
| Fargate Spot target capacity units | 1,000 | L-FE37DB9B | Fargate Spot requests exceeding this fail — affects cost-optimized fleets |
Monitoring running task count requires Container Insights. Enable it at the cluster level:
const cluster = new ecs.Cluster(this, 'McpCluster', {
clusterName: 'alivemcp-prod',
containerInsights: true, // enables ECS Container Insights metrics
vpc,
});
// Container Insights publishes ECS/ContainerInsights RunningTaskCount automatically
// Alarm at 80% of the per-cluster task quota
const fargateTaskAlarm = new cloudwatch.Alarm(this, 'FargateTaskQuotaAlarm', {
alarmName: 'alivemcp-fargate-task-quota-80pct',
metric: new cloudwatch.Metric({
namespace: 'ECS/ContainerInsights',
metricName: 'RunningTaskCount',
dimensionsMap: { ClusterName: 'alivemcp-prod' },
period: cdk.Duration.minutes(1),
statistic: 'Maximum',
}),
threshold: 500 * 0.8, // 80% of default quota (adjust after approved increases)
evaluationPeriods: 3,
datapointsToAlarm: 2,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
Diagnosing quota exhaustion in ECS
When ECS Fargate task scheduling fails due to quota exhaustion, the ECS service shows tasks in PROVISIONING state. Use the ECS service event log to find quota error messages:
# Check service events for quota errors
aws ecs describe-services \
--cluster alivemcp-prod \
--services alivemcp-mcp-server \
--query 'services[0].events[:10].{CreatedAt:createdAt,Message:message}'
# Count running tasks to compare against quota
aws ecs list-tasks \
--cluster alivemcp-prod \
--desired-status RUNNING | \
jq '.taskArns | length'
# Get the per-cluster Fargate task quota
aws service-quotas get-service-quota \
--service-code ecs \
--quota-code L-3794C6DC \
--query 'Quota.Value'
Lambda quota breakdown
Lambda quotas affect MCP servers that use Lambda as the tool handler runtime (via HTTP adapter or direct invocation from ECS). The concurrent executions quota is shared across all Lambda functions in the account — a noisy Lambda batch job can exhaust the quota and cause MCP tool call failures even if the MCP-specific Lambdas are well within their expected concurrency.
| Quota name | Default | Quota code | Impact when exhausted |
|---|---|---|---|
| Concurrent executions (account-wide) | 1,000 | L-B99A9384 | TooManyRequestsException on all Lambda invocations in the account |
| Provisioned concurrency utilization | Scales with concurrent executions quota | L-DC2196D0 | Cannot provision warm Lambda instances beyond this limit |
| Function storage (ephemeral /tmp) | 10 GB (default 512 MB per function, adjustable up to 10 GB) | L-2ACBD22F | Tool handlers writing large temp files fail with ENOSPC |
| Deployment package size (direct upload) | 50 MB (direct) / 250 MB (S3) | N/A (hard limit) | Large Lambda bundles must be deployed via S3 not direct upload |
Using reserved concurrency to protect MCP tool handlers
Reserved concurrency dedicates a portion of the account-wide concurrency pool exclusively to a specific function. If the account runs other Lambda workloads (data pipelines, webhooks, cron jobs), use reserved concurrency on MCP tool handler functions to prevent account-wide throttles from affecting MCP availability:
const mcpToolHandler = new lambda.Function(this, 'McpToolHandler', {
runtime: lambda.Runtime.NODEJS_22_X,
code: lambda.Code.fromAsset('tool-handler'),
handler: 'index.handler',
// Reserve 200 out of the account's 1,000 concurrent executions
// Other Lambda functions in the account share the remaining 800
reservedConcurrentExecutions: 200,
memorySize: 512,
timeout: cdk.Duration.seconds(30),
});
// Monitor reserved concurrency utilization separately
const reservedConcurrencyAlarm = new cloudwatch.Alarm(this, 'ReservedConcurrencyAlarm', {
alarmName: 'alivemcp-mcp-handler-reserved-concurrency-80pct',
metric: new cloudwatch.Metric({
namespace: 'AWS/Lambda',
metricName: 'ConcurrentExecutions',
dimensionsMap: { FunctionName: mcpToolHandler.functionName },
period: cdk.Duration.minutes(1),
statistic: 'Maximum',
}),
threshold: 200 * 0.8, // 80% of reserved pool
evaluationPeriods: 3,
datapointsToAlarm: 2,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
Reserved concurrency has a cost: a function with reservedConcurrentExecutions: 200 can never scale beyond 200 concurrent executions regardless of the account quota. If the MCP server experiences a burst that requires more than 200 concurrent handlers, calls will be throttled. Set the reserved value at 80–90% of the expected peak, and monitor for throttles on the reserved function separately to know when to increase the reservation.
Provisioned concurrency for cold-start-sensitive MCP handlers
MCP protocol establishes a session before the first tool call. If the Lambda handler has a cold start of several seconds, the session establishment adds perceptible latency. Provisioned concurrency pre-warms Lambda instances to eliminate cold starts:
const handlerVersion = mcpToolHandler.currentVersion;
const provisionedConfig = new lambda.ProvisionedConcurrencyConfig(this, 'McpHandlerProvisionedConcurrency', {
target: handlerVersion,
provisionedConcurrentExecutions: 10, // keep 10 warm instances at all times
});
// Provisioned concurrency alarm — alert if more than 80% of provisioned instances are in use
// (remaining 20% available for burst before cold starts begin)
const provisionedUtilizationAlarm = new cloudwatch.Alarm(this, 'ProvisionedConcurrencyAlarm', {
alarmName: 'alivemcp-provisioned-concurrency-utilization-80pct',
metric: new cloudwatch.Metric({
namespace: 'AWS/Lambda',
metricName: 'ProvisionedConcurrencyUtilization',
dimensionsMap: { FunctionName: mcpToolHandler.functionName },
period: cdk.Duration.minutes(1),
statistic: 'Maximum',
}),
threshold: 80, // 80% of provisioned pool in use
evaluationPeriods: 5,
datapointsToAlarm: 3,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
Quota increase request timing
The optimal time to request a quota increase is when usage reaches 60%, not 80%. Quota increase approvals take 1–3 business days for common quotas. If you wait until 80% alarm fires to submit a request and the approval takes 3 days, you may hit 100% and experience service disruption during the approval window. For quotas on critical scaling paths (Lambda concurrent executions, Fargate running tasks per cluster), include the following in your increase request to accelerate approval:
- Current usage pattern and growth trajectory (e.g., "we are growing at 50% month-over-month in concurrent sessions")
- Specific use case and why the higher quota is needed (e.g., "each MCP server session requires a separate Lambda invocation for tool isolation")
- Target value with justification (request 2–3× current quota, not 10×; large requests take longer to review)
# Request Lambda concurrent executions increase with justification note
aws service-quotas request-service-quota-increase \
--service-code lambda \
--quota-code L-B99A9384 \
--desired-value 3000 \
# Note: add justification in the resulting Support case
# Case ID is returned in --query 'RequestedQuota.CaseId'
AWS does not provide a free-text justification field in RequestServiceQuotaIncrease. The API creates a Support case — add the justification text as a reply to that case using the Support console or the aws support add-communication-to-case CLI command.
Common failures
| Symptom | Cause | Fix |
|---|---|---|
ECS service shows tasks in PROVISIONING indefinitely | Fargate task quota (per-cluster or per-region) exhausted; ECS cannot schedule new tasks | Check aws ecs describe-services --services SERVICE --query 'services[0].events' for quota error messages; count running tasks and compare to quota; request increase via Service Quotas API |
Lambda returns TooManyRequestsException even at low function-level concurrency | Account-wide concurrent executions quota exhausted by other Lambda functions | Use aws lambda get-account-settings --query ConcurrentExecutions to see account-wide usage; add reserved concurrency to MCP handlers; request quota increase; optionally move noisy Lambdas to a separate AWS account |
| Provisioned concurrency alarm fires even when usage appears low | Metric is ProvisionedConcurrencyUtilization (percentage), not ProvisionedConcurrencyAllocated (count) | Verify that the metric dimensions specify FunctionName not just the function's ARN — Alias dimensions are required if provisioned concurrency is on an alias rather than a version |
Container Insights RunningTaskCount not appearing in CloudWatch | Container Insights not enabled on the cluster, or the cluster was created before Container Insights was enabled | Enable via aws ecs update-cluster-settings --cluster CLUSTER --settings name=containerInsights,value=enabled; metrics appear within a few minutes of the next ECS task state change |