feat: native OAuth login page, architecture docs, docs site update
- Add GET/POST /login to hermes for first-party cookie during OAuth popup (fixes browser CHIPS cookie partitioning that broke claude.ai connection) - Add role column to all findCustomer* SQL queries in src/auth.ts - Add claude.ai tab to docs/getting-started.html with OAuth flow steps - Add ARCHITECTURE.md with system diagrams, data flow, and key invariants - Rewrite README.md and DEPLOY.md to reflect actual MicroK8s deployment - Deploy updated docs site (squaremcp-docs sha256 updated) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
275
DEPLOY.md
275
DEPLOY.md
@@ -1,155 +1,68 @@
|
||||
# Hermes MCP — Setup & Deployment
|
||||
# Hermes MCP — Deployment Runbook
|
||||
|
||||
Hermes is a multi-account email MCP server for Claude AI.
|
||||
It supports **Yahoo Mail** (IMAP App Password) and any **custom IMAP/SMTP server**.
|
||||
Production deployment runs on a single-node MicroK8s cluster in the `fetcherpay` namespace. Three services are deployed: `hermes-mcp` (API), `squaremcp-app` (SaaS UI), `squaremcp-docs` (docs).
|
||||
|
||||
---
|
||||
|
||||
## Local development
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
npm install
|
||||
|
||||
# 2. Copy and fill in credentials
|
||||
cp .env.example .env
|
||||
# edit .env with your email credentials
|
||||
|
||||
# 3. Run in dev mode (hot-reload)
|
||||
npm run dev
|
||||
|
||||
# 4. Test health
|
||||
curl http://localhost:3456/health
|
||||
# → {"status":"ok","service":"hermes-mcp"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production deployment (Kubernetes example)
|
||||
|
||||
Example deployment using MicroK8s single-node cluster.
|
||||
SSL is handled by `cert-manager` with a Let's Encrypt certificate.
|
||||
|
||||
### Prerequisites on the server
|
||||
- MicroK8s with addons: `dns`, `ingress`, `registry`, `cert-manager`
|
||||
- Local registry at `localhost:32000`
|
||||
- A `ClusterIssuer` named `letsencrypt-prod` already configured
|
||||
- Local image registry at `localhost:32000`
|
||||
- `ClusterIssuer` named `letsencrypt-prod` configured
|
||||
- MySQL 8 running as Docker container on host (`127.0.0.1:3306`)
|
||||
- Redis 7 running as Docker container on host (`127.0.0.1:6379`)
|
||||
|
||||
---
|
||||
|
||||
## Deploying hermes-mcp
|
||||
|
||||
### One-time: create K8s namespace and secret
|
||||
```bash
|
||||
microk8s kubectl create namespace hermes-mcp # if it doesn't exist
|
||||
# 1. Build TypeScript
|
||||
npm run build
|
||||
|
||||
microk8s kubectl create secret generic hermes-mcp-env -n hermes-mcp \
|
||||
--from-literal=YAHOO_EMAIL=your@yahoo.com \
|
||||
--from-literal=YAHOO_APP_PASSWORD=your-app-password \
|
||||
--from-literal=CUSTOM_EMAIL=your@domain.com \
|
||||
--from-literal=CUSTOM_PASSWORD=your-password \
|
||||
--from-literal=CUSTOM_IMAP_HOST=mail.yourdomain.com \
|
||||
--from-literal=CUSTOM_IMAP_PORT=993 \
|
||||
--from-literal=CUSTOM_SMTP_HOST=mail.yourdomain.com \
|
||||
--from-literal=CUSTOM_SMTP_PORT=587 \
|
||||
--from-literal=PORT=3456
|
||||
```
|
||||
|
||||
### Build & push image
|
||||
```bash
|
||||
# Option 1: Build locally and push to your registry
|
||||
# 2. Build and push Docker image
|
||||
docker build -t localhost:32000/hermes-mcp:latest .
|
||||
docker push localhost:32000/hermes-mcp:latest
|
||||
|
||||
# Option 2: Copy to server and build there
|
||||
scp -r src/ package*.json tsconfig.json Dockerfile user@your-server:~/hermes-mcp/
|
||||
ssh user@your-server "cd ~/hermes-mcp && docker build -t localhost:32000/hermes-mcp:latest . && docker push localhost:32000/hermes-mcp:latest"
|
||||
```
|
||||
# 3. Get the sha256 digest from push output, or:
|
||||
docker inspect localhost:32000/hermes-mcp:latest \
|
||||
--format='{{index .RepoDigests 0}}'
|
||||
|
||||
### Apply K8s manifests
|
||||
```yaml
|
||||
# hermes-k8s.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hermes-mcp
|
||||
namespace: hermes-mcp
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: hermes-mcp
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: hermes-mcp
|
||||
spec:
|
||||
containers:
|
||||
- name: hermes-mcp
|
||||
image: localhost:32000/hermes-mcp:latest
|
||||
ports:
|
||||
- containerPort: 3456
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: hermes-mcp-env
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3456
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: hermes-mcp
|
||||
namespace: hermes-mcp
|
||||
spec:
|
||||
selector:
|
||||
app: hermes-mcp
|
||||
ports:
|
||||
- port: 3456
|
||||
targetPort: 3456
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: hermes-ingress
|
||||
namespace: hermes-mcp
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: hermes.yourdomain.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: hermes-mcp
|
||||
port:
|
||||
number: 3456
|
||||
tls:
|
||||
- hosts:
|
||||
- hermes.yourdomain.com
|
||||
secretName: hermes-tls
|
||||
```
|
||||
# 4. Update hermes-k8s.yaml
|
||||
# Change: image: localhost:32000/hermes-mcp@sha256:<old>
|
||||
# To: image: localhost:32000/hermes-mcp@sha256:<new>
|
||||
|
||||
Apply the manifests:
|
||||
```bash
|
||||
# 5. Apply
|
||||
microk8s kubectl apply -f hermes-k8s.yaml
|
||||
|
||||
# 6. Wait for rollout
|
||||
microk8s kubectl rollout status deployment/hermes-mcp -n fetcherpay
|
||||
```
|
||||
|
||||
### Redeploy after code changes
|
||||
```bash
|
||||
# Rebuild and push the image
|
||||
docker build -t localhost:32000/hermes-mcp:latest .
|
||||
docker push localhost:32000/hermes-mcp:latest
|
||||
**Important:** `hermes-k8s.yaml` is gitignored — it contains plaintext secrets. Never force-add it to git. Always use sha256 digest (never `:latest`) in the manifest.
|
||||
|
||||
# Restart the deployment
|
||||
microk8s kubectl rollout restart deployment/hermes-mcp -n hermes-mcp
|
||||
microk8s kubectl rollout status deployment/hermes-mcp -n hermes-mcp
|
||||
---
|
||||
|
||||
## Deploying squaremcp-app (dashboard UI)
|
||||
|
||||
```bash
|
||||
docker build -f product/app/Dockerfile . -t localhost:32000/squaremcp-app:latest
|
||||
docker push localhost:32000/squaremcp-app:latest
|
||||
|
||||
# Update sha256 in product/app/app-k8s.yaml, then:
|
||||
microk8s kubectl apply -f product/app/app-k8s.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deploying squaremcp-docs
|
||||
|
||||
```bash
|
||||
docker build -f docs/Dockerfile . -t localhost:32000/squaremcp-docs:latest
|
||||
docker push localhost:32000/squaremcp-docs:latest
|
||||
|
||||
# Update sha256 in docs/docs-k8s.yaml, then:
|
||||
microk8s kubectl apply -f docs/docs-k8s.yaml
|
||||
```
|
||||
|
||||
---
|
||||
@@ -157,48 +70,80 @@ microk8s kubectl rollout status deployment/hermes-mcp -n hermes-mcp
|
||||
## Useful commands
|
||||
|
||||
```bash
|
||||
# Logs
|
||||
microk8s kubectl logs -n hermes-mcp -l app=hermes-mcp --tail=100 -f
|
||||
# Check pod status
|
||||
microk8s kubectl get pods -n fetcherpay
|
||||
|
||||
# Pod status
|
||||
microk8s kubectl get pods -n hermes-mcp -l app=hermes-mcp
|
||||
|
||||
# Update a single env var without rebuild (takes effect on next rollout)
|
||||
microk8s kubectl set env deployment/hermes-mcp -n hermes-mcp KEY=value
|
||||
microk8s kubectl rollout restart deployment/hermes-mcp -n hermes-mcp
|
||||
# Live logs
|
||||
microk8s kubectl logs -n fetcherpay -l app=hermes-mcp -f --tail=100
|
||||
|
||||
# Health check
|
||||
curl https://hermes.yourdomain.com/health
|
||||
curl https://hermes.squaremcp.com/health
|
||||
|
||||
# Inject env var without rebuild (takes effect after rollout)
|
||||
microk8s kubectl set env deployment/hermes-mcp -n fetcherpay KEY=value
|
||||
microk8s kubectl rollout restart deployment/hermes-mcp -n fetcherpay
|
||||
|
||||
# Restart all pods
|
||||
microk8s kubectl rollout restart deployment -n fetcherpay
|
||||
|
||||
# Check TLS certificates
|
||||
microk8s kubectl get certificate -n fetcherpay
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Add to Claude.ai
|
||||
## Environment variables (hermes-mcp)
|
||||
|
||||
1. Go to **Claude.ai → Settings → Connectors → Add custom connector**
|
||||
2. Enter URL: `https://hermes.yourdomain.com/mcp` (or your server's URL)
|
||||
3. Click Connect
|
||||
Key variables in `hermes-k8s.yaml`:
|
||||
|
||||
### Available tools
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `PORT` | Server port (3456) |
|
||||
| `SERVER_URL` | Public base URL (`https://hermes.squaremcp.com`) |
|
||||
| `MCP_API_KEY` | Global superadmin API key |
|
||||
| `MYSQL_HOST/PORT/USER/PASSWORD` | MySQL connection |
|
||||
| `REDIS_URL` | Redis connection (`redis://127.0.0.1:6379`) |
|
||||
| `CREDENTIAL_ENCRYPTION_KEY` | AES-256-GCM key for stored platform credentials |
|
||||
| `OAUTH_CLIENT_ID/SECRET` | Pre-registered OAuth app credentials |
|
||||
| `OBSIDIAN_VAULT_PATH` | Mount path for Obsidian vault (`/vaults`) |
|
||||
| `YAHOO_EMAIL / YAHOO_APP_PASSWORD` | Default Yahoo IMAP account |
|
||||
| `GMAIL_EMAIL / GMAIL_APP_PASSWORD` | Default Gmail account |
|
||||
| `FETCHERPAY_*` | Fetcherpay email IMAP/SMTP |
|
||||
|
||||
| Tool | Description | Key params |
|
||||
|------|-------------|------------|
|
||||
| `get_profile` | Get email address for an account | `account` |
|
||||
| `search_messages` | Search INBOX by keyword/sender/subject | `q`, `maxResults`, `account` |
|
||||
| `read_message` | Read full message body by UID | `uid`, `account` |
|
||||
| `list_folders` | List all mailbox folders | `account` |
|
||||
| `create_draft` | Save a draft to the Drafts folder | `to`, `subject`, `body`, `account` |
|
||||
| `send_email` | Send an email | `to`, `subject`, `body`, `account` |
|
||||
|
||||
`account` is always optional and defaults to `"yahoo"`. Configure your second account in the code if needed.
|
||||
**Do not rotate `CREDENTIAL_ENCRYPTION_KEY`** without first re-encrypting all stored customer credentials in Redis.
|
||||
|
||||
---
|
||||
|
||||
## Known issues & fixes
|
||||
## Schema migrations
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `read_message` timeout | `source: true` downloads full raw RFC822 | Use `bodyParts: ['TEXT']` instead |
|
||||
| `messageFlagsAdd` deadlock | Called inside `for await` loop while FETCH active | Moved to after the loop |
|
||||
| Stale session after pod restart | Session ID guard blocked re-initialize | Accept initialize regardless of session ID |
|
||||
| `EAI_AGAIN` DNS errors | K8s internal DNS resolution issues | Use direct IP instead of hostname |
|
||||
`src/db.ts` uses `ensureColumn()` — an idempotent helper that `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`. Run migrations by deploying a new image; the startup hook runs automatically.
|
||||
|
||||
To run a migration manually:
|
||||
```bash
|
||||
mysql -h 127.0.0.1 -u root -pfetcherpay hermes_oauth
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Domains and TLS
|
||||
|
||||
| Domain | K8s Ingress | TLS secret |
|
||||
|--------|-------------|------------|
|
||||
| `hermes.squaremcp.com` | `hermes-ingress` | `hermes-squaremcp-tls` |
|
||||
| `app.squaremcp.com` | `squaremcp-app-ingress` | `squaremcp-app-tls` |
|
||||
| `docs.squaremcp.com` | `squaremcp-docs-ingress` | `squaremcp-docs-tls` |
|
||||
|
||||
TLS certificates are auto-provisioned by cert-manager from Let's Encrypt. Check certificate status:
|
||||
```bash
|
||||
microk8s kubectl describe certificate -n fetcherpay
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
To roll back to the previous image, update the sha256 digest in the manifest to the previous value and re-apply:
|
||||
```bash
|
||||
microk8s kubectl apply -f hermes-k8s.yaml
|
||||
microk8s kubectl rollout status deployment/hermes-mcp -n fetcherpay
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user