Guide · CloudWatch · Service Quotas · Lambda · ECS · DynamoDB

CloudWatch Quota Usage Alarms for MCP Servers — Alert Before Hitting Service Limits

AWS publishes quota consumption metrics to the AWS/Usage CloudWatch namespace automatically — no instrumentation required. The metrics track how close your account is to each service quota in real time. For MCP server operators, this means you can create an alarm that fires when Lambda concurrent executions reach 80% of the account limit, giving you time to request an increase before the quota is exhausted and tool calls start failing. This guide covers the AWS/Usage metric structure, the Service Quotas console integration for one-click alarm creation, CDK automation for quota alarms at deployment time, and the specific alarm configurations for the quotas most likely to affect MCP server scaling.

TL;DR

The AWS/Usage namespace publishes ResourceCount metrics with dimensions Service, Resource, Type=Resource, and Class=None. Use a CloudWatch MathExpression alarm combining ResourceCount (current usage) with the quota value from aws service-quotas get-service-quota to alarm at 80% utilization. Alternatively, use the Service Quotas console "Create CloudWatch Alarm" button on any quota's detail page — it creates the same math alarm automatically. Set treatMissingData: notBreaching on quota alarms because periods where no usage data exists should not trigger alerts.

The AWS/Usage namespace

The AWS/Usage CloudWatch namespace is populated automatically by AWS for all services that have service quotas. You do not need to enable it or deploy any instrumentation. Each metric has these dimensions:

DimensionValueNotes
ServiceAWS service name (e.g., Lambda)Use PascalCase service name, not the CLI service code
ResourceResource type (e.g., Executions)Matches the quota's resource type
TypeAlways ResourceFilter on this to avoid spurious matches
ClassAlways NoneFilter on this to avoid spurious matches

Not all quotas have a corresponding AWS/Usage metric. DynamoDB table count and ECS task count do not publish usage metrics automatically. For those, use the quota automation approach which queries the resource count directly and publishes a custom metric.

Lambda concurrent executions quota alarm

Lambda concurrent executions is the quota most likely to silently fail MCP servers at scale. When the account-level limit is exhausted, Lambda returns TooManyRequestsException — the same exception as function-level throttling, but the cause is completely different and the fix is a quota increase, not function optimization.

import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as cdk from 'aws-cdk-lib';

// Lambda concurrent executions: default quota is 1,000
// Adjust quotaValue to match your account's applied quota from:
// aws service-quotas get-service-quota --service-code lambda --quota-code L-B99A9384
const LAMBDA_CONCURRENCY_QUOTA = 1000;

const lambdaConcurrencyUsage = new cloudwatch.Metric({
  namespace: 'AWS/Usage',
  metricName: 'ResourceCount',
  dimensionsMap: {
    Service: 'Lambda',
    Resource: 'Executions',
    Type: 'Resource',
    Class: 'None',
  },
  period: cdk.Duration.minutes(1),
  statistic: 'Maximum',
});

// Math expression: usage / quota * 100 = percentage consumed
const lambdaConcurrencyPct = new cloudwatch.MathExpression({
  expression: 'm1 / LAMBDA_QUOTA * 100',
  usingMetrics: { m1: lambdaConcurrencyUsage },
  period: cdk.Duration.minutes(1),
  label: 'Lambda concurrency % of quota',
});
// MathExpression variables via usingMetrics only support metric references; pass quota as expression constant
const lambdaConcurrencyPctAlarm = new cloudwatch.Alarm(this, 'LambdaConcurrencyQuotaAlarm', {
  alarmName: 'alivemcp-lambda-concurrency-quota-80pct',
  alarmDescription: `Lambda concurrent executions exceeding 80% of quota (${LAMBDA_CONCURRENCY_QUOTA})`,
  metric: lambdaConcurrencyUsage,
  threshold: LAMBDA_CONCURRENCY_QUOTA * 0.8,
  evaluationPeriods: 3,
  datapointsToAlarm: 2,
  comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
  treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});

The alarm uses the raw ResourceCount metric with a threshold set to 80% of the quota value. When the quota value changes (after an approved increase request), update the LAMBDA_CONCURRENCY_QUOTA constant and redeploy the CDK stack — the alarm threshold updates automatically. Use evaluationPeriods: 3 and datapointsToAlarm: 2 to avoid false alarms from brief concurrency spikes.

Using Service Quotas console alarm creation

The Service Quotas console provides a "Create CloudWatch Alarm" button on each quota's detail page. This is the fastest way to create a quota alarm without CDK — it creates a CloudWatch alarm with the correct math expression pre-filled, including the current quota value as the threshold denominator. The alarm uses the same AWS/Usage metric and fires at 80% consumption by default (you can adjust the threshold).

To use it: navigate to Service Quotas → AWS Lambda → Concurrent executions → Create CloudWatch Alarm. The resulting alarm will be named ServiceQuotaUsageAlarm-lambda-L-B99A9384 by default. For infrastructure-as-code deployments, prefer the CDK approach above to keep alarms consistent with your stack.

ECS Fargate running tasks alarm via custom metric

ECS running task count per cluster does not publish a AWS/Usage metric. Use a custom metric approach: a periodic Lambda that queries the ECS API and publishes a MCPServerQuota/ECSRunningTasks custom metric, then alarm on that metric.

// quota-monitor-lambda/index.ts
import { ECSClient, ListTasksCommand } from '@aws-sdk/client-ecs';
import { ServiceQuotasClient, GetServiceQuotaCommand } from '@aws-sdk/client-service-quotas';
import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch';

const ecs = new ECSClient({});
const quotas = new ServiceQuotasClient({});
const cloudwatch = new CloudWatchClient({});

export const handler = async () => {
  const cluster = process.env.CLUSTER_ARN!;

  // Count running tasks
  let totalTasks = 0;
  let nextToken: string | undefined;
  do {
    const result = await ecs.send(new ListTasksCommand({
      cluster,
      desiredStatus: 'RUNNING',
      nextToken,
    }));
    totalTasks += result.taskArns?.length ?? 0;
    nextToken = result.nextToken;
  } while (nextToken);

  // Get the quota
  const quota = await quotas.send(new GetServiceQuotaCommand({
    ServiceCode: 'ecs',
    QuotaCode: 'L-3794C6DC',  // Running tasks per cluster (Fargate)
  }));
  const limit = quota.Quota?.Value ?? 500;

  // Publish current usage and quota as custom metrics
  await cloudwatch.send(new PutMetricDataCommand({
    Namespace: 'MCPServerQuota',
    MetricData: [
      {
        MetricName: 'ECSRunningTasks',
        Dimensions: [{ Name: 'ClusterArn', Value: cluster }],
        Value: totalTasks,
        Unit: 'Count',
      },
      {
        MetricName: 'ECSRunningTasksQuota',
        Dimensions: [{ Name: 'ClusterArn', Value: cluster }],
        Value: limit,
        Unit: 'Count',
      },
      {
        MetricName: 'ECSRunningTasksPercent',
        Dimensions: [{ Name: 'ClusterArn', Value: cluster }],
        Value: (totalTasks / limit) * 100,
        Unit: 'Percent',
      },
    ],
  }));
};

Schedule this Lambda every 5 minutes via EventBridge Scheduler or EventBridge Rules. Create an alarm on the MCPServerQuota/ECSRunningTasksPercent metric with a threshold of 80.

// Alarm on the custom ECS quota percent metric
new cloudwatch.Alarm(this, 'ECSTasksQuotaAlarm', {
  alarmName: 'alivemcp-ecs-tasks-quota-80pct',
  metric: new cloudwatch.Metric({
    namespace: 'MCPServerQuota',
    metricName: 'ECSRunningTasksPercent',
    dimensionsMap: { ClusterArn: cluster.clusterArn },
    period: cdk.Duration.minutes(5),
    statistic: 'Maximum',
  }),
  threshold: 80,
  evaluationPeriods: 2,
  datapointsToAlarm: 2,
  treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});

Quota alarm dashboard

Group all quota alarms into a single CloudWatch dashboard for a consolidated view of approaching limits:

const quotaDashboard = new cloudwatch.Dashboard(this, 'QuotaDashboard', {
  dashboardName: 'alivemcp-quota-health',
});

quotaDashboard.addWidgets(
  new cloudwatch.AlarmStatusWidget({
    title: 'Quota Alarms',
    alarms: [
      lambdaConcurrencyPctAlarm,
      ecsTasksQuotaAlarm,
      // Add more quota alarms here
    ],
    width: 24,
  }),
  new cloudwatch.GraphWidget({
    title: 'Lambda Concurrent Executions vs Quota',
    left: [lambdaConcurrencyUsage],
    leftAnnotations: [
      { value: LAMBDA_CONCURRENCY_QUOTA, label: 'Quota limit', color: '#ff0000' },
      { value: LAMBDA_CONCURRENCY_QUOTA * 0.8, label: '80% threshold', color: '#ff8800' },
    ],
    width: 12,
  }),
);

Common failures

SymptomCauseFix
AWS/Usage ResourceCount metric shows no data in CloudWatchThe service does not publish usage metrics for that quota type, or the dimensions are wrongVerify exact dimension values using the CloudWatch Metrics console browser — dimension values are case-sensitive; Service: Lambda not Service: lambda
Quota alarm fires immediately on deployment even though service is under quotatreatMissingData: breaching set — alarm treats periods with no data as a breachChange to treatMissingData: notBreaching for quota alarms; quota metrics only emit data points when there is actual usage
Quota alarm threshold is incorrect after approved quota increaseThe CDK constant LAMBDA_CONCURRENCY_QUOTA was not updated after the increase was approvedQuery the applied quota with aws service-quotas get-service-quota and update the constant; alternatively, use a MathExpression that calls the quota API directly via a custom metric Lambda
ECS running task count metric shows stale valuesLambda schedule interval is 5 minutes but tasks scale faster than the refresh rateReduce EventBridge schedule to 1 minute; use ECS Container Insights RunningTaskCount metric instead if Container Insights is enabled — it updates every minute automatically