Skip to main content
Glama
kishoreqwerty

CloudGuard MCP

README.md
# CloudGuard MCP

A read-only Model Context Protocol (MCP) server for AWS resource inspection,
misconfiguration detection, and cost estimation. It gives an MCP-compatible
agent (Claude, or any other MCP client) a safe, structured way to query
EC2, S3, and IAM state and to reason about it across multiple tool calls,
without granting the agent any ability to modify or delete infrastructure.

![CloudGuard MCP — System Architecture](docs/architecture.png)

## Problem

Answering a question like "which S3 buckets are missing encryption, and
what would it cost to fix them" normally means opening the AWS console,
checking each bucket by hand, and cross-referencing Cost Explorer manually.
An LLM agent cannot do this today because it has no safe, structured
interface into a live AWS account. This project builds that interface.

## Design principles

- **Read-only by construction, not by convention.** Every AWS call in this
  project is enforced read-only at two independent layers: the IAM policy
  attached to the credentials (`iam/cloudguard-readonly-policy.json`), and
  an application-level check in `aws_client.py` that refuses to invoke any
  boto3 operation not prefixed `describe_`, `get_`, or `list_`, regardless
  of what the IAM policy allows. Either layer failing independently still
  results in no mutating call reaching AWS.
- **Compound detectors, not just raw tools.** Individual tools
  (`list_s3_buckets`, `get_bucket_encryption_status`, ...) are composed into
  detector functions (`find_unencrypted_buckets`, `run_security_audit`, ...)
  that perform the multi-step orchestration server-side, so a calling agent
  can ask a compound question in one tool call instead of re-deriving the
  same chain of calls on every query.
- **Cached and throttle-aware.** All AWS calls go through a shared, TTL-cached
  client with exponential backoff on throttling, so repeated or overlapping
  agent queries don't hammer the AWS API.
- **Fully paginated.** `describe_instances`, `describe_security_groups`,
  `list_roles`, `list_role_policies`, and `list_buckets` all support
  pagination in the underlying AWS APIs. Every one of these calls goes
  through `AWSClient.call_paginated`, which exhausts all pages before
  returning — an account with more resources than the default page
  size would otherwise silently produce incomplete results, not just
  slow ones. See `tests/test_pagination.py` for a test that forces
  multi-page responses and confirms nothing is dropped.
- **Typed contracts.** Every tool input and output is a Pydantic model
  (`schemas.py`), not a raw dict, so the tool interface is self-documenting
  and validated at the boundary.

## Architecture

For a full component diagram and an end-to-end request-flow sequence
diagram, see [`docs/architecture.md`](docs/architecture.md).

```
src/cloudguard_mcp/
    aws_client.py       Cached, safety-enforced boto3 wrapper. All AWS
                         calls in the project go through this module.
    schemas.py           Typed request/response models for every tool.
    tools/
        ec2_tools.py      EC2 instance + security group inspection.
        s3_tools.py       S3 bucket, encryption, and public-access checks.
        iam_tools.py      IAM role and inline-policy risk checks.
        cost_tools.py     Cost Explorer spend-by-service queries.
    detectors/
        misconfiguration.py   Compound detectors built by chaining the
                               tools above (e.g. find_unencrypted_buckets).
    server.py             The MCP server: registers every tool/resource
                           above with the MCP protocol via FastMCP.
iam/
    cloudguard-readonly-policy.json   The IAM policy to attach to whatever
                                       credentials run this server.
tests/                    pytest + moto test suite. No real AWS account or
                           credentials are required to run the tests.
```

## Available tools

`list_ec2_instances` (and the detectors built on it, `find_open_security_groups`
and `find_idle_ec2_instances`) accept a `regions` list and scan every region
in it concurrently, tagging each returned instance with the region it was
found in. This is EC2-specific: S3 (`list_s3_buckets`) and IAM
(`list_iam_roles`) are account-global AWS APIs, not region-scoped, so those
tools are unaffected by multi-region support. A failure in any single
region (throttling, a transient error, missing permissions in that region)
does not abort the scan or discard results from regions that succeeded —
it's recorded separately and the scan degrades gracefully. See
`tests/test_multi_region_error_isolation.py`.

| Tool | Description |
|---|---|
| `list_ec2_instances` | List EC2 instances across one or more regions; flags instances with a sensitive port open to 0.0.0.0/0 |
| `list_s3_buckets` | List all S3 buckets |
| `get_bucket_encryption_status` | Check default encryption on a bucket |
| `get_bucket_public_access_status` | Check public-access-block configuration on a bucket |
| `list_iam_roles` | List IAM roles; flags wildcard Action/Resource grants in inline policies |
| `get_cost_by_service` | Total cost grouped by AWS service over a trailing window |
| `find_unencrypted_buckets` | Compound: buckets missing default encryption |
| `find_public_buckets` | Compound: buckets not fully blocking public access |
| `find_overpermissioned_iam_roles` | Compound: roles with Action:`*` on Resource:`*` |
| `find_open_security_groups` | Compound: instances open to the internet on a sensitive port |
| `find_idle_ec2_instances` | Compound: stopped instances, with recent EC2 spend as context |
| `run_security_audit` | Runs every detector above and returns the combined finding list |

Plus one MCP resource, `cloudguard://account/inventory`, exposing a
browsable snapshot of the account's inspected EC2/S3/IAM state.

## Setup

```bash
pip install -e ".[dev]"
```

### Running the tests (no AWS account required)

The full test suite runs against `moto`, an in-memory AWS mock — no real
credentials, network access, or cost.

```bash
pytest tests/ -v
```

### Running against a real AWS account

1. Create a dedicated IAM user or role and attach the policy in
   `iam/cloudguard-readonly-policy.json`.
2. Configure credentials for that identity (e.g. `aws configure`, or the
   standard `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_DEFAULT_REGION`
   environment variables).
3. Run the server:

```bash
python -m cloudguard_mcp.server
```

Point an MCP client at this process over stdio to begin issuing tool calls.

## Demo

`demo/run_demo.py` connects a real Claude Opus 5 model to this MCP server
over stdio (against a seeded, moto-mocked AWS account, so it's runnable
without real AWS credentials) and lets it chain tool calls on its own to
answer compound questions. See
[docs/demo_transcript.md](docs/demo_transcript.md) for a full writeup of a
real run, including the agent parallelizing independent tool calls, refusing
to fabricate a per-bucket cost figure when the data wasn't available, and
independently catching that one detector's cost estimate was aggregate
account spend rather than per-instance.

## What this project does not do

This project does not modify, create, or delete any AWS resource under any
circumstance. It does not replace dedicated security posture tools such as
AWS Config, Prowler, or ScoutSuite for comprehensive compliance scanning;
its scope is deliberately narrow (a handful of common, high-signal
misconfigurations) in favor of exposing that scope through a well-designed,
agent-composable MCP interface rather than a large, static rule set.