Skip to content

Commit 8e06faf

Browse files
brendanbabbclaude
andcommitted
Add AWS Lambda packaging and Terraform infrastructure
Packages mcp-server as an AWS Lambda function deployable via Terraform. The bulk of the change is infrastructure: terraform/aws/ defines the Lambda, API Gateway, RDS Postgres, Secrets Manager, and optional ACM custom domain; terraform/bootstrap/ provisions the S3 + DynamoDB state backend; scripts/deploy.sh builds, packages, and applies in one shot. Server-side changes to support Lambda: - Split entry point: createServer.ts owns server construction; index.ts remains the local dev entry; lambda.ts is the new API Gateway handler. - database.service.ts pulls RDS credentials from Secrets Manager when DB_SECRET_ARN is set, falling back to env vars for local dev. - Adds the serverless-express adapter as a runtime dep. .gitignore excludes Terraform state, plans, .deploy/ build dir, the lambda-deployment.zip artifact, and local .claude/ settings. README documents the deploy workflow and prereqs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cc03b5f commit 8e06faf

25 files changed

Lines changed: 2827 additions & 172 deletions

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ tmp/
2020
# IDE specific files
2121
.idea/
2222
.vscode/
23+
.claude/
24+
25+
# Terraform
26+
.terraform/
27+
*.tfstate
28+
*.tfstate.*
29+
tfplan
30+
31+
# Secrets
32+
.secret.tmp.json
33+
34+
# Lambda deployment artifacts
35+
.deploy/
36+
lambda-deployment.zip
2337

2438
# System files
2539
.DS_Store

README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The *U.S. Census Bureau Data API MCP* is a [Model Context Protocol (MCP)](https:
1414
## Contents
1515
* [Getting Started](#getting-started)
1616
* [Using the MCP Server](#using-the-mcp-server)
17+
* [Deploy to AWS Lambda](#deploy-to-aws-lambda)
1718
* [How the MCP Server Works](#how-the-mcp-server-works)
1819
* [Development](#development)
1920
* [MCP Server Architecture](#mcp-server-architecture)
@@ -23,6 +24,110 @@ The *U.S. Census Bureau Data API MCP* is a [Model Context Protocol (MCP)](https:
2324
* [Helper Scripts](#helper-scripts)
2425
* [Additional Information](#additional-information)
2526

27+
## Deploy to AWS Lambda
28+
29+
This fork ports the stdio MCP server to a public HTTP endpoint backed by AWS
30+
Lambda + RDS Postgres, patterned after [CityOfBoston/OpenContext](https://github.com/CityOfBoston/OpenContext).
31+
32+
**Architecture.** `POST /mcp` → API Gateway REST API → Lambda (Node 20, zip
33+
package) → RDS Postgres (public subnet, SSL required). Lambda is NOT inside a
34+
VPC — it reaches RDS and `api.census.gov` over the public internet to avoid a
35+
NAT Gateway (~$32/mo). RDS is public-subnet with SG `0.0.0.0/0:5432`; protection
36+
is SSL + a 32-char password stored in Secrets Manager, fetched by Lambda on
37+
cold start.
38+
39+
**Target cost.** ~$15/mo steady state (db.t4g.micro + 20 GB gp3 + Secrets
40+
Manager + Lambda free tier).
41+
42+
### Prerequisites
43+
44+
- AWS CLI configured for account `420839047325` / region `us-west-2`
45+
- Terraform ≥ 1.0
46+
- Node 18+, npm
47+
- A Census Data API key — https://api.census.gov/data/key_signup.html
48+
49+
### One-time setup
50+
51+
```bash
52+
# 1. Bootstrap Terraform remote state (S3 + DynamoDB)
53+
./scripts/setup-backend.sh
54+
55+
# 2. Export your Census API key (the deploy script injects it into Lambda env)
56+
export CENSUS_API_KEY="your-key-here"
57+
58+
# 3. Deploy staging
59+
./scripts/deploy.sh --environment staging
60+
```
61+
62+
The deploy script runs `terraform plan`, shows the diff, and asks for
63+
confirmation before applying.
64+
65+
### Seed the database
66+
67+
After the first `terraform apply`, RDS is empty. Pull the connection string
68+
from Secrets Manager and run the existing `mcp-db` migration + seed pipeline
69+
against RDS (~10–20 minutes, throttled by the Census API):
70+
71+
```bash
72+
SECRET_ARN=$(cd terraform/aws && terraform output -raw db_secret_arn)
73+
74+
# Fetch and format as a Postgres URL
75+
export DATABASE_URL=$(aws secretsmanager get-secret-value \
76+
--secret-id "$SECRET_ARN" \
77+
--query SecretString --output text | \
78+
node -e 'const s=JSON.parse(require("fs").readFileSync(0,"utf8"));
79+
process.stdout.write(`postgresql://${encodeURIComponent(s.username)}:${encodeURIComponent(s.password)}@${s.host}:${s.port}/${s.dbname}?sslmode=require`)')
80+
81+
cd mcp-db
82+
npm ci
83+
npm run migrate:up
84+
npm run seed
85+
```
86+
87+
The RDS security group allows `0.0.0.0/0:5432` by default so your laptop can
88+
reach it. If you want to tighten this, edit `terraform/aws/rds.tf` to scope the
89+
ingress to your IP and re-apply — but remember to re-open it (or run from a
90+
machine with a stable IP) when you need to re-seed later.
91+
92+
### Connect Claude
93+
94+
Get the endpoint and add it as a custom connector in Claude Desktop or
95+
Claude.ai (Settings → Connectors → Add custom connector):
96+
97+
```bash
98+
cd terraform/aws && terraform output -raw api_gateway_url
99+
```
100+
101+
### Tear down
102+
103+
```bash
104+
cd terraform/aws
105+
terraform destroy -var-file="staging.tfvars" -var="census_api_key=$CENSUS_API_KEY"
106+
```
107+
108+
The S3 state bucket and DynamoDB lock table (created by `terraform/bootstrap`)
109+
are retained with `prevent_destroy = true` — remove that lifecycle block and
110+
re-apply the bootstrap stack if you really want them gone.
111+
112+
### Security posture — read this before using for anything real
113+
114+
- **Public-subnet RDS** with `0.0.0.0/0:5432` ingress. Protected by SSL +
115+
strong password, not the security group. Acceptable for a personal test
116+
deployment. NOT acceptable for production or sensitive data.
117+
- **No auth on the MCP endpoint.** The API Gateway uses
118+
`authorization = "NONE"` — anyone who finds the URL can call it until daily
119+
quota kicks in (default 3000 req/day, 5 req/s). Rotate the URL or raise the
120+
quota wall if you post it publicly.
121+
- **Census API key** lives inside the same Secrets Manager secret as the RDS
122+
credentials (key `census_api_key`). The Lambda fetches it on cold start and
123+
sets `process.env.CENSUS_API_KEY` before any tool runs. Not visible to
124+
`lambda:GetFunction` callers — only to principals with
125+
`secretsmanager:GetSecretValue` on the secret ARN. Rotate by editing the
126+
secret version; the next cold start picks it up.
127+
- **Lambda has internet egress** because it's not in a VPC. That's the whole
128+
point — it avoids a NAT Gateway — but means the function can reach anything
129+
on the public internet. Not a concern unless a tool is compromised.
130+
26131
## Getting Started
27132
To get started, you will need:
28133

0 commit comments

Comments
 (0)