EmailTemplateMCP
by theaidrill
README.md
# EmailTemplateMCP
A minimal [MCP](https://modelcontextprotocol.io) server that generates email templates with Claude
— one tool, one file, deployed as a single AWS Lambda function behind a public Function URL. No
framework, no MCP SDK dependency: the JSON-RPC dispatch is ~130 lines of plain JavaScript.
This is the reference implementation used in the *"Build an MCP server from scratch"* video series.
## What it does
Exposes a single tool, `create_email_template`, which takes a description of an email's purpose
(plus optional tone and brand name) and returns a subject line, a plain-text body, and an
inline-styled HTML body — generated by Claude in one call.
```json
{
"name": "create_email_template",
"arguments": {
"description": "welcome email for new SaaS signups",
"tone": "friendly",
"brand_name": "Acme"
}
}
```
## Why no MCP SDK?
MCP isn't a library you install — it's a protocol: JSON-RPC 2.0 over HTTP. This project implements
`initialize`, `tools/list`, and `tools/call` directly in the Lambda handler, so there's nothing to
learn beyond reading `index.mjs` top to bottom. The only real dependency is the
[Anthropic SDK](https://github.com/anthropics/anthropic-sdk-typescript), used to actually generate
the email content.
Output shape is enforced server-side via Anthropic's structured output
(`output_config.format.json_schema`), so there's no fragile hand-rolled JSON parsing of the model's
response.
## Why a Function URL, not API Gateway?
Generating an email is a real model call — it can take 10-20+ seconds. API Gateway's HTTP API has a
hard, non-configurable 30-second integration timeout. A Lambda Function URL has no such ceiling; the
only limit is the function's own configured timeout (set here to ~2 minutes).
## Deploying
### Option A — one command, via AWS SAM
`template.yaml` in this repo defines the whole thing — the function, its Function URL, and both
environment variables — so you don't have to click through the console at all.
```bash
sam build
sam deploy --guided
```
`--guided` walks you through stack name, region, and the two secret parameters (`AnthropicApiKey`,
`McpApiKey` — both `NoEcho`, never printed or stored in plain text in the template) on first run,
then remembers your answers in `samconfig.toml` for future deploys. On success it prints the
Function URL as an output.
Requires the [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
and valid AWS credentials configured locally.
### Option B — manual, via the Lambda console
1. `npm install` — pulls in the Anthropic SDK.
2. Zip the folder's **contents** (not the folder itself) — `index.mjs`, `node_modules`,
`package.json`, `package-lock.json` should sit at the zip's top level.
3. In the Lambda console: **Create function** → author from scratch → Node.js 22+ → upload the zip.
4. **Configuration → General configuration**: raise Timeout (e.g. 2 min) and Memory (e.g. 256 MB) —
the defaults (3s / 128MB) aren't enough for a real generation call.
5. **Configuration → Environment variables**:
- `ANTHROPIC_API_KEY` — from the Anthropic console.
- `MCP_API_KEY` — a secret you invent (any long random string). This is the shared secret every
caller must present.
- `ANTHROPIC_MODEL` — optional, defaults to `claude-opus-5`.
6. **Configuration → Function URL**: create one with Auth type `NONE`. This doesn't mean
unauthenticated — it means AWS isn't checking IAM signatures. `MCP_API_KEY` is the actual gate,
checked on every request inside the handler.
## Authentication
Every request must include the shared secret, either as a header:
```
x-api-key: <MCP_API_KEY>
```
or a query parameter:
```
?key=<MCP_API_KEY>
```
Requests without a valid key get a `401` before anything else runs — including before any call to
Anthropic, so a bad key never costs you a generation.
## Testing it directly
```bash
# List tools
curl -s https://<your-function-url> \
-H "x-api-key: <MCP_API_KEY>" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Call the tool
curl -s https://<your-function-url> \
-H "x-api-key: <MCP_API_KEY>" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"create_email_template","arguments":{"description":"welcome email for new signups"}}}'
```
## Connecting an MCP client
For Claude Code:
```bash
claude mcp add --transport http email-template https://<your-function-url> \
--header "x-api-key: <MCP_API_KEY>"
```
Any MCP-compatible client that supports plain header auth over Streamable HTTP works the same way.
## Project structure
```
index.mjs — the entire server: tool definition, Anthropic call, JSON-RPC dispatch
package.json — the one runtime dependency
```
## Security note
`MCP_API_KEY` and `ANTHROPIC_API_KEY` are both plain environment variables — never hardcode them in
`index.mjs`. If you've ever shown this function's configuration on screen (a demo, a screen
recording), rotate both values afterward.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues