# Hermes MCP — Setup & Deployment Hermes is a multi-account email MCP server for Claude AI. It supports **Yahoo Mail** (IMAP App Password) and any **self-hosted mail server** (Dovecot / Poste.io). --- ## Local development ```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 (MicroK8s — current setup) The server runs as a Kubernetes Deployment on `23.120.207.35` (MicroK8s single-node cluster). SSL is handled by `cert-manager` with a Let's Encrypt cert for `hermes.fetcherpay.com`. ### Prerequisites on the server - MicroK8s with addons: `dns`, `ingress`, `registry`, `cert-manager` - Local registry at `localhost:32000` - A `ClusterIssuer` named `letsencrypt-prod` already configured ### One-time: create K8s namespace secret ```bash microk8s kubectl create namespace fetcherpay # if it doesn't exist microk8s kubectl create secret generic hermes-mcp-env -n fetcherpay \ --from-literal=YAHOO_EMAIL=gheron01@yahoo.com \ --from-literal=YAHOO_APP_PASSWORD=lzlleytmslxocxae \ --from-literal=FETCHERPAY_EMAIL=garfield.heron@fetcherpay.com \ --from-literal=FETCHERPAY_PASSWORD=onelove \ --from-literal=FETCHERPAY_IMAP_HOST=23.120.207.35 \ --from-literal=FETCHERPAY_IMAP_PORT=30993 \ --from-literal=FETCHERPAY_SMTP_HOST=23.120.207.35 \ --from-literal=FETCHERPAY_SMTP_PORT=30587 \ --from-literal=PORT=3456 ``` ### Build & push image ```bash # From your local machine — SCP src to server first, then SSH in: scp -P 2222 -r src/ package*.json tsconfig.json Dockerfile garfield@23.120.207.35:~/hermes-mcp/ ssh -p 2222 garfield@23.120.207.35 cd ~/hermes-mcp docker build -t localhost:32000/hermes-mcp:latest . docker push localhost:32000/hermes-mcp:latest ``` ### Apply K8s manifests (hermes-k8s.yaml on the server) ```yaml # ~/hermes-mcp/hermes-k8s.yaml (already applied — shown for reference) apiVersion: apps/v1 kind: Deployment metadata: name: hermes-mcp namespace: fetcherpay 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: fetcherpay spec: selector: app: hermes-mcp ports: - port: 3456 targetPort: 3456 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: hermes-ingress namespace: fetcherpay 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.fetcherpay.com http: paths: - path: / pathType: Prefix backend: service: name: hermes-mcp port: number: 3456 tls: - hosts: - hermes.fetcherpay.com secretName: hermes-fetcherpay-tls ``` ### Redeploy after code changes ```bash # On your local machine: scp -P 2222 src/imap.ts src/smtp.ts src/tools.ts src/index.ts \ garfield@23.120.207.35:~/hermes-mcp/src/ ssh -p 2222 garfield@23.120.207.35 " cd ~/hermes-mcp && docker build -t localhost:32000/hermes-mcp:latest . && docker push localhost:32000/hermes-mcp:latest && microk8s kubectl rollout restart deployment/hermes-mcp -n fetcherpay && microk8s kubectl rollout status deployment/hermes-mcp -n fetcherpay " ``` --- ## Useful commands ```bash # Logs microk8s kubectl logs -n fetcherpay -l app=hermes-mcp --tail=100 -f # Pod status microk8s kubectl get pods -n fetcherpay -l app=hermes-mcp # Update a single env var without rebuild (takes effect on next rollout) microk8s kubectl set env deployment/hermes-mcp -n fetcherpay KEY=value microk8s kubectl rollout restart deployment/hermes-mcp -n fetcherpay # Health check curl https://hermes.fetcherpay.com/health ``` --- ## Add to Claude.ai 1. Go to **Claude.ai → Settings → Connectors → Add custom connector** 2. Enter URL: `https://hermes.fetcherpay.com/mcp` 3. Click Connect ### Available tools | 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 — defaults to `"yahoo"`. Set to `"fetcherpay"` for the FetcherPay mailbox. --- ## Known issues & fixes | Issue | Cause | Fix | |-------|-------|-----| | `yahoo_read_message` 5-min timeout | `source: true` downloads full raw RFC822 | Use `bodyParts: ['TEXT']` | | `messageFlagsAdd` deadlock | Called inside `for await` loop while FETCH active | Moved to after the loop | | Stale session after pod restart | `!sessionId` guard blocked re-initialize | Accept initialize regardless of session ID | | FetcherPay `EAI_AGAIN` DNS | K8s internal DNS cold-start for hostname | Use direct IP `23.120.207.35` |