Content
# MCP Registry Infrastructure
A sample for a secure, enterprise-grade infrastructure for hosting MCP (Model Context Protocol) registry files using AWS CloudFront, S3, WAF, and CloudFront signed URLs.
## Overview
This sample solution provides a complete infrastructure-as-code deployment for hosting an MCP registry JSON file that Kiro and other AI assistant clients can use to discover approved MCP servers. The architecture follows AWS security best practices with three access control modes:
- **Public**: CloudFront serves the registry without access restrictions
- **Restricted**: WAF filters requests based on source IP addresses
- **Signed**: CloudFront signed URLs require a cryptographic token to access the registry (can be combined with IP filtering for defense-in-depth)
## Architecture

## Features
- **Private S3 Bucket**: All public access blocked, server-side encryption with AES-256
- **CloudFront CDN**: Global edge caching with HTTPS-only access and TLS 1.2 minimum
- **Origin Access Control (OAC)**: Secure S3 access using SigV4 signing
- **Optional WAF IP Filtering**: Restrict access to specific IP ranges
- **Optional Signed URL Authentication**: Token-based access via CloudFront signed URLs with automated key lifecycle management
- **Idempotent Deployment**: Safe to run multiple times without errors
- **Cache Invalidation**: Automatic CloudFront cache invalidation on updates
## Prerequisites
- AWS CLI configured with appropriate credentials
- `jq` installed for JSON parsing
- Bash shell (macOS, Linux, or WSL on Windows)
## Quick Start
### 1. Deploy in Public Mode
```bash
./deploy.sh \
--stack-name my-mcp-registry \
--registry-file ./mcp-registry.json \
--mode public
```
### 2. Deploy with IP Restrictions
```bash
./deploy.sh \
--stack-name my-mcp-registry \
--registry-file ./mcp-registry.json \
--mode restricted \
--allowed-ips "10.0.0.0/8,192.168.1.0/24,203.0.113.50/32"
```
### 3. Deploy with Signed URL Authentication
```bash
./deploy.sh \
--stack-name my-mcp-registry \
--registry-file ./mcp-registry.json \
--mode signed
```
### 4. Deploy with Signed URL + IP Filtering (Defense-in-Depth)
```bash
./deploy.sh \
--stack-name my-mcp-registry \
--registry-file ./mcp-registry.json \
--mode signed \
--allowed-ips "10.0.0.0/8,192.168.1.0/24"
```
## Files
| File | Description |
|------|-------------|
| `mcp-registry-infrastructure.yaml` | CloudFormation template defining all AWS resources |
| `deploy.sh` | Idempotent deployment script |
| `mcp-registry.json` | Sample MCP registry with AWS servers |
| `lambda_handler/index.py` | Lambda custom resource for signed URL key lifecycle |
| `validate_registry.py` | Schema validation script for registry files |
## Deploy Script Options
```
Usage: deploy.sh [OPTIONS]
Required Options:
--stack-name <name> CloudFormation stack name
--registry-file <path> Path to mcp-registry.json file
--mode <public|restricted|signed> Access control mode
Optional Options:
--allowed-ips <cidrs> Comma-separated CIDR ranges (for restricted or signed+IP mode)
--region <region> AWS region (default: us-east-1)
--skip-validation Skip registry file schema validation before upload
-h, --help Display help message
Exit Codes:
0 - Success
1 - Invalid arguments
2 - CloudFormation deployment failed
3 - S3 upload failed
4 - CloudFront invalidation failed
```
## MCP Registry Format
The registry file follows the MCP registry JSON schema:
```json
{
"servers": [
{
"server": {
"name": "server-name",
"description": "Server description",
"version": "1.0.0",
"packages": [
{
"registryType": "pypi",
"identifier": "package-name@version",
"transport": {
"type": "stdio"
}
}
]
}
}
]
}
```
### Validating Your Registry File
```bash
uv run --with jsonschema python validate_registry.py mcp-registry.json
```
## Updating the Registry
To update the registry after initial deployment, run the deploy script again with the updated registry file:
```bash
./deploy.sh \
--stack-name my-mcp-registry \
--registry-file ./mcp-registry.json \
--mode public
```
The script will:
1. Update the CloudFormation stack (if template changed)
2. Validate the registry file against the MCP schema
3. Upload the new registry file to S3
4. Invalidate the CloudFront cache
## CloudFormation Outputs
After deployment, the stack provides these outputs:
| Output | Description |
|--------|-------------|
| `BucketName` | S3 bucket name for direct uploads |
| `DistributionId` | CloudFront distribution ID |
| `DistributionDomainName` | CloudFront domain (e.g., `d1234567890.cloudfront.net`) |
| `MCPRegistryURL` | Full URL to the registry file |
| `WebACLArn` | WAF Web ACL ARN (restricted mode only) |
| `SignedRegistryURL` | Secrets Manager ARN containing the signed URL (signed mode only) |
| `PrivateKeySecretArn` | Secrets Manager ARN for the RSA private key (signed mode only) |
## Security
### S3 Bucket Security
- All public access blocked (BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets)
- Server-side encryption with AES-256
- Access logging enabled to a dedicated log bucket (90-day retention)
- Bucket policy allows only CloudFront OAC access
### CloudFront Security
- HTTPS-only (ViewerProtocolPolicy: https-only)
- TLS 1.2 minimum protocol version
- GET and HEAD methods only
- Origin Access Control with SigV4 signing
- Standard access logging enabled for request-level visibility
### WAF Security (Restricted Mode)
- IP-based allow list with default-block policy
- Custom 403 JSON response for blocked requests
- CloudWatch metrics enabled for monitoring
### Signed URL Security (Signed Mode)
- RSA 2048-bit key pair generated by Lambda custom resource
- Private key stored in AWS Secrets Manager (never leaves your account)
- CloudFront validates signature, expiry, and key pair ID on every request
- Treat the signed URL as a secret credential — do not commit to version control
## Signed URL Rotation
When using `--mode signed`, the signed URL acts as a bearer credential. Rotate it periodically or immediately if you suspect compromise.
### Quick Rotation via Redeployment
The simplest approach — redeploy the stack and the Lambda handles the full key lifecycle automatically:
```bash
./deploy.sh --stack-name my-mcp-registry \
--registry-file ./mcp-registry.json \
--mode signed
```
The stack update replaces the old key group with a new one and outputs a fresh signed URL. Update the URL in your AI assistant's admin console (e.g., Kiro enterprise governance settings). There is a brief moment during the update where the old key group is removed before the new one is active — for a registry JSON file, this is typically acceptable.
### Zero-Downtime Rotation (Manual)
For environments where even brief interruptions are unacceptable, you can manually add a second key group alongside the existing one using the AWS CLI. CloudFront supports up to 4 trusted key groups simultaneously.
**Step 1:** Generate a new key pair and register it with CloudFront
**Step 2:** Add the new key group to the distribution alongside the old one
**Step 3:** Update the admin console with the new signed URL
**Step 4:** Remove the old key group and clean up resources
See the [detailed guide](GUIDE.md) for CLI commands for each step.
### Emergency Revocation
```bash
# Revoke immediately by switching to public mode
./deploy.sh --stack-name my-mcp-registry \
--registry-file ./mcp-registry.json --mode public
# Then redeploy with a fresh signed URL
./deploy.sh --stack-name my-mcp-registry \
--registry-file ./mcp-registry.json --mode signed
```
### Recommended Rotation Schedule
| Environment | Expiry | Rotation Frequency |
|-------------|--------|--------------------|
| Development | 365 days | Every 6 months |
| Production | 90 days | Every 60 days |
| High security | 30 days | Every 21 days |
## Troubleshooting
### Deployment Fails
- Verify AWS credentials are configured: `aws sts get-caller-identity`
- Check CloudFormation events in AWS Console for detailed errors
- Ensure the stack name is unique in your account/region
### Registry Not Updating
- CloudFront cache invalidation takes 1-5 minutes to propagate
- Check invalidation status: `aws cloudfront list-invalidations --distribution-id <ID>`
### Access Denied (403)
- In restricted mode, verify your IP is in the allowed list
- In signed mode, verify the signed URL hasn't expired
- Check WAF logs in CloudWatch for blocked requests
## Cleanup
To delete all resources:
```bash
# Empty the registry bucket
aws s3 rm s3://$(aws cloudformation describe-stacks \
--stack-name my-mcp-registry \
--query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' \
--output text) --recursive
# Empty the access log bucket
aws s3 rm s3://$(aws cloudformation describe-stacks \
--stack-name my-mcp-registry \
--query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' \
--output text)-access-logs --recursive
# Delete the stack
aws cloudformation delete-stack --stack-name my-mcp-registry
```
## Cost Warning
This deployment creates AWS resources that will incur costs. The primary cost drivers are:
- Amazon CloudFront distribution (request pricing + data transfer)
- Amazon S3 storage and requests
- AWS WAF (if using restricted or signed+IP mode)
- AWS Secrets Manager (if using signed mode)
- AWS Lambda invocations (if using signed mode, only during deployment)
For a single JSON file with moderate traffic, costs are minimal. Use the [AWS Pricing Calculator](https://calculator.aws/) to estimate costs for your specific usage.
## License
This project is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file.
## Contributing
See [CONTRIBUTING](CONTRIBUTING.md) for guidelines.
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.