Files
hermes-mcp/DEPLOY.md
garfieldheron 356b6b9f55 Initial commit: Hermes MCP - Yahoo Mail server for Claude AI
- Multi-account email support (Yahoo + self-hosted IMAP/SMTP)
- MCP tools: get_profile, search_messages, read_message, list_folders, create_draft, send_email
- Streamable HTTP transport with session recovery
- Docker + Kubernetes deployment configuration
- Express server with health endpoint

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-05 13:14:30 -05:00

5.8 KiB

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

# 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

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

# 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)

# ~/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

# 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

# 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