# One Tool Call Away From a $10,000 AWS Bill

> The AWS MCP server exposes 55 tools for EC2, S3, Lambda, and RDS. Here's how to block destructive operations and rate limit resource creation.

Published: Mon Mar 16

Canonical: https://policylayer.com/blog/secure-aws-mcp-server

It's 2 AM. Your coding agent is refactoring a microservice. It decides the best path forward is to "clean up" the staging environment — deletes three S3 buckets, tears down a CloudFormation stack, and spins up eight `m5.4xlarge` instances to "run integration tests faster." By morning, the bill is north of $10,000. The agent wasn't malicious. It was helpful. That's what makes it dangerous.

The [AWS MCP server](https://github.com/awslabs/mcp) from AWS Labs gives agents direct access to 55 tools spanning EC2, S3, Lambda, DynamoDB, EKS, CloudFormation, and Terraform. Read access, write access, and — crucially — delete access. All through a single MCP connection. No confirmation dialogs. No spending caps. Just tool calls.

<!--truncate-->

## 55 Tools, Including Delete and Destroy

The `awslabs/mcp` server exposes tools that fall into four categories:

- **Read tools** (25+): `list_resources`, `get_resource`, `describe_log_groups`, `search_documentation` — harmless, let them run
- **Create tools**: `create_resource`, `create_table_from_csv` — each one potentially spinning up billable infrastructure
- **Execute tools**: `call_aws`, `invoke_lambda`, `tf_apply`, `manage_eks_stacks` — arbitrary API calls and infrastructure changes
- **Destructive tools**: `delete_resource`, `tf_destroy` — the ones that keep you up at night

The problem isn't that these tools exist. They're genuinely useful. The problem is that nothing stops an agent from calling `delete_resource` on your production S3 bucket or `create_resource` in a loop until you've provisioned a hundred EC2 instances. [System prompts don't enforce anything](/blog/ai-agent-goes-rogue) — they're suggestions the model can ignore, misinterpret, or override when it thinks it knows better.

## Block Destructive Ops, Rate Limit Creation

PolicyLayer sits between your agent and the AWS MCP server as a transparent proxy. Every `tools/call` passes through it and gets evaluated against a YAML policy before reaching upstream. Here's what the key sections of the AWS policy look like:

**Hard-block deletions:**

```yaml
delete_resource:
    rules:
      - name: "block-delete"
        action: deny
        on_deny: "Resource deletion blocked by policy"

tf_destroy:
    rules:
      - name: "block-destroy"
        action: deny
        on_deny: "Infrastructure destruction blocked by policy"
```

No conditions, no exceptions. `delete_resource` and `tf_destroy` are denied unconditionally. The agent gets back a clear error message explaining why. It can carry on with its work using every other tool — it just can't delete anything.

**Rate limit resource creation:**

```yaml
create_resource:
    rules:
      - name: "rate-limit-create"
        rate_limit: "10/hour"
        on_deny: "Rate limit: max 10 resource creations per hour"
```

The agent can still create resources, but no more than 10 per hour. PolicyLayer tracks this with a stateful counter (SQLite by default, Redis for multi-instance setups). The counter persists across restarts, so killing and restarting the proxy doesn't reset the budget.

**Rate limit write operations:**

```yaml
call_aws:
    rules:
      - name: "rate-limit-writes"
        rate_limit: "30/hour"
        on_deny: "Rate limit: max 30 write operations per hour"
```

The same pattern applies to `call_aws`, `invoke_lambda`, `tf_apply`, `manage_eks_stacks`, and `update_resource` — anything that mutates state gets capped at 30 operations per hour.

**Global safety net:**

```yaml
"*":
    rules:
      - name: "global-rate-limit"
        rate_limit: "120/minute"
        on_deny: "Global rate limit: max 120 calls per minute"
```

The wildcard rule catches everything, including read operations. Even if the agent goes into a tight loop calling `list_resources`, it'll get throttled before it hammers the AWS API hard enough to trigger their own rate limits (and the associated costs of retries and backoff).

Read tools like `describe_log_groups`, `search_documentation`, and `get_resource` have no rules at all — they pass straight through. No point adding friction to operations that cost nothing and change nothing.

## Getting Started

Route your AWS MCP server through PolicyLayer — point your MCP client at the gateway URL with a per-person grant token, and the policy above runs on every call before it reaches AWS:

```json
{
  "mcpServers": {
    "aws": {
      "url": "https://proxy.policylayer.com/mcp/<server-uuid>/",
      "headers": { "Authorization": "Bearer <grant-token>" }
    }
  }
}
```

You define and adjust the policy in the [PolicyLayer dashboard](https://app.policylayer.com) — no local proxy to install or run.

The difference is that every call now passes through your policy before reaching AWS. [Spending controls that actually enforce](/blog/spending-controls-mcp-agent), not suggest.

The full AWS policy covers all 55 tools with categorised rules, comments explaining each tool's purpose, and tuneable rate limits. Use it as-is or as a starting point.

[Full AWS policy &rarr;](/policies/aws)
