Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions mcp/saas-status-mcp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# CDK output
cdk.out*

# Python
__pycache__/
*.pyc
*.pyo
.venv/
venv/
*.egg-info/

# IDE
.idea/
.vscode/
*.swp

# OS
.DS_Store
Thumbs.db

# Environment
.env
.env.local

# Generated by deploy-all — contains account-specific runtime ARN
local-proxy/mcp.json

# Build artifacts
build/

# Terraform state and generated files
infrastructure/terraform/.terraform/
infrastructure/terraform/.terraform.lock.hcl
infrastructure/terraform/terraform.tfvars
infrastructure/terraform/terraform.tfstate
infrastructure/terraform/terraform.tfstate.backup
122 changes: 122 additions & 0 deletions mcp/saas-status-mcp/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Architecture — SaaS Status MCP Server

## Overview

This MCP server bridges AWS DevOps Agent's internal investigation capabilities with the external SaaS health signals that live outside AWS. It runs as a stateless Python server hosted on Amazon Bedrock AgentCore Runtime, exposing four MCP tools that DevOps Agent can call mid-investigation to correlate infrastructure signals with upstream dependency status.

---

## High-Level Architecture

```
┌──────────────────┐ ┌─────────────────────────┐ ┌────────────────────┐
│ AWS DevOps │ MCP │ AgentCore Runtime │ HTTPS │ Statuspage.io │
│ Agent │───────>│ (saas-status-mcp) │───────>│ Public APIs │
│ (Investigation) │ │ │ │ (no auth needed) │
└──────────────────┘ └─────────────────────────┘ └────────────────────┘
│ Conditional GET (ETag)
┌─────────────────────┐
│ S3: providers.json │
│ (live registry) │
└─────────────────────┘
```

### Request flow

1. DevOps Agent is investigating an alert and decides to check upstream dependencies.
2. It invokes the MCP server via the `bedrock-agentcore:InvokeAgentRuntime` API, signing with SigV4.
3. AgentCore Runtime routes the call to the MCP server process over `streamable-http` on port 8000.
4. The server fans out concurrent HTTPS requests to the relevant Statuspage.io public API endpoints.
5. Results are normalized and returned as structured JSON to DevOps Agent.

---

## Components

### AgentCore Runtime

The hosting layer. AgentCore Runtime manages the container lifecycle, IAM authentication, and the MCP protocol transport so the server code has no AWS SDK calls in the hot path — it only does outbound HTTP.

- **Transport**: `streamable-http` (stateless, required by AgentCore Runtime)
- **Network mode**: `PUBLIC` — the runtime makes outbound calls to public Statuspage.io endpoints; no VPC needed
- **Entrypoint**: `main.py` via `FastMCP`
- **Runtime environment**: Python 3.13

### MCP Server (`agent/`)

| File | Responsibility |
|------|---------------|
| `main.py` | FastMCP app definition; declares the four `@mcp.tool` functions; binds to `0.0.0.0:8000` |
| `tools.py` | Tool implementations; `check_all_dependencies` fans requests out with `asyncio.gather` |
| `statuspage_client.py` | Async HTTP client (`httpx`) for the Statuspage.io `/api/v2/*` contract |
| `config.py` | S3-backed provider registry with ETag-based conditional GET — avoids reloading unless the file changes |
| `providers.json` | Source-controlled seed registry (28 providers); uploaded to S3 on first deploy |

### Provider Registry (S3)

The registry is a JSON array of `{name, display_name, statuspage_url}` objects. It is stored in S3 at `s3://saas-status-mcp-<account>-<region>/config/providers.json` and read by the server at startup and then re-checked every 60 seconds via a conditional GET (using the `ETag` and `If-None-Match` headers). If the object has not changed, S3 returns a `304 Not Modified` and the server keeps its cached copy — zero read cost on steady state.

This design allows operators to update the live provider list by pushing a new `providers.json` to S3 (via `refresh-providers.ps1/.sh`) without touching code or redeploying.

### IAM

| Role | Principal | Permissions |
|------|-----------|-------------|
| `SaasStatusMcpRuntimeRole` | `bedrock-agentcore.amazonaws.com` | `s3:GetObject` on deployment bucket; `logs:PutLogEvents` on `/aws/bedrock-agentcore/runtimes/*` |
| SigV4 signing role (registration stack) | `aidevops.amazonaws.com` | `bedrock-agentcore:InvokeAgentRuntime` on the runtime ARN |

DevOps Agent assumes the signing role when invoking the runtime. The runtime itself assumes the runtime role to read from S3 and write logs.

### CloudWatch Logs

Structured JSON logs from the server are written to `/aws/bedrock-agentcore/runtimes/*` with a 14-day retention policy. The log group is torn down on stack destroy (`RemovalPolicy.DESTROY`).

---

## CDK Stacks

| Stack | Deployed to | Purpose |
|-------|-------------|---------|
| `SaasStatusMcpStack-{region}` | Runtime region | AgentCore Runtime, runtime IAM role, CloudWatch log group |
| `SaasStatusMcpRegistrationStack-{space-region}` | Agent Space region | SigV4 signing role, DevOps Agent Service, Association |

The registration stack is optional and only deployed when you run `setup-devops-agent`. The two stacks can target different regions — the runtime ARN is exported from the main stack and imported by the registration stack.

---

## Design Decisions

### Stateless by design

There is no database and no persisted state. Every tool call is a fresh read from Statuspage.io. This keeps the server simple, eliminates stale-data bugs, and makes horizontal scaling trivial — AgentCore Runtime can spin up multiple instances without coordination.

### Single Statuspage.io client covers 80%+ of providers

Most major SaaS vendors (Snowflake, Datadog, GitHub, MongoDB, PagerDuty, etc.) run on Atlassian Statuspage.io, which exposes a uniform public REST API at `/api/v2/status.json`, `/api/v2/incidents/unresolved.json`, and `/api/v2/scheduled-maintenances/active.json`. One generic client handles all of them — no provider-specific code, and adding a new provider is a JSON entry in the registry with no code change.

### Parallel fan-out in `check_all_dependencies`

`asyncio.gather` is used to fire all provider requests concurrently. For a 10-provider bulk check, wall-clock time is the max of individual response times rather than their sum — typically under 2 seconds.

### ETag-based config caching

The provider registry is polled every 60 seconds using `If-None-Match` / `ETag` headers. On steady state (no registry change) S3 returns `304 Not Modified` with no body — avoiding both unnecessary data transfer and stale-config latency without a cache invalidation mechanism.

### SigV4 authentication at the runtime boundary

The AgentCore Runtime endpoint is not a public HTTP API. All callers must sign requests with `bedrock-agentcore:InvokeAgentRuntime`. The MCP server code itself is unaware of authentication — IAM is enforced at the runtime layer. Local clients (e.g. Kiro) use `local-proxy/proxy.py`, a stdio-to-SigV4-HTTP bridge that signs requests with the local AWS credentials.

---

## Local Development (Kiro)

```
┌──────────────┐ stdio ┌─────────────────┐ SigV4/HTTPS ┌───────────────────────┐
│ Kiro MCP │────────>│ local-proxy/ │─────────────>│ AgentCore Runtime │
│ client │ │ proxy.py │ │ (deployed) │
└──────────────┘ └─────────────────┘ └───────────────────────┘
```

The proxy bridges the stdio transport expected by local MCP clients to the SigV4-signed HTTPS transport required by AgentCore Runtime. `deploy-all` generates `local-proxy/mcp.json` with the runtime ARN and region pre-filled — this file is gitignored since it contains account-specific values.
58 changes: 58 additions & 0 deletions mcp/saas-status-mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Changelog

All notable changes to the SaaS Status MCP server are documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] — 2026-08-31

Initial release.

### Added

- **Four provider-agnostic MCP tools** for correlating AWS DevOps Agent
investigations with upstream SaaS health:
- `list_providers` — returns every provider in the registry (local read, no
external call).
- `get_service_status` — current overall Statuspage.io indicator for one
provider, normalized so `none` maps to `operational`.
- `get_active_events` — the core investigation tool; merges unresolved
incidents and active scheduled maintenances into a single normalized event
list, with optional `include_history` for full update trails.
- `check_all_dependencies` — bulk status + active-event count across up to 10
providers, fanned out in parallel with `asyncio.gather`.
- **Generic Statuspage.io client.** A single async `httpx` client speaks the
public `/api/v2/*` contract (`status.json`, `incidents/unresolved.json`,
`scheduled-maintenances/active.json`) — covering 80%+ of major SaaS providers
with no provider-specific code and no authentication.
- **28-provider seed registry** (`agent/providers.json`) covering Snowflake,
Datadog, MongoDB, GitHub, PagerDuty, and more.
- **S3-backed live registry with conditional GET.** The running server reads the
provider registry from S3 using ETag / `If-None-Match`, so operators can add or
remove providers by pushing a new `providers.json` (via `refresh-providers`)
with no redeploy. Local development falls back to the repo-local seed.
- **Stateless AgentCore Runtime hosting.** Deployed to Amazon Bedrock AgentCore
Runtime over the `streamable-http` transport (`stateless_http=True`,
`json_response=True`), `PUBLIC` network mode, Python 3.13 — no VPC, no database,
every call a fresh read.
- **SigV4 security model.** The runtime is IAM-protected; DevOps Agent assumes a
dedicated signing role scoped to `bedrock-agentcore:InvokeAgentRuntime` on the
runtime ARN, with a trust policy limited to `aidevops.amazonaws.com` in the
caller's account and Agent Space region.
- **Two IaC paths.** CDK (Python) and Terraform, both producing the same runtime
stack plus an optional DevOps Agent registration stack (SigV4 signing role,
`AWS::DevOpsAgent::Service`, `AWS::DevOpsAgent::Association` enabling the four
tools).
- **One-command deploy scripts** for Windows (PowerShell) and macOS/Linux (bash),
covering both the CDK and Terraform paths, plus `setup-devops-agent` for
registration and `refresh-providers` for live registry updates.
- **Optional stdio testing bridge** (`local-proxy/proxy.py`) that lets a local MCP
client exercise the deployed runtime by SigV4-signing calls with local AWS
credentials — a testing aid only, not a supported production client.
- **Unit tests** (`tests/test_tools.py`) covering all four tools with mocked
Statuspage.io responses (operational, degraded, active incident, active
maintenance, history, bulk-check, and the 10-provider cap), plus an end-to-end
`invoke_test.py` against a deployed runtime.

[1.0.0]: https://github.com/aws/tools-for-devops-agent
Loading