feat: standalone claude-memory-mcp with multi-user support and Vault integration

Extracted from private infra repo into standalone open-source project.

Three operating modes:
- Local: SQLite + FTS5 (zero dependencies)
- Server: PostgreSQL via HTTP API with multi-user auth
- Full: PostgreSQL + HashiCorp Vault for secret management

Features:
- MCP stdio server with 5 tools (store/recall/list/delete/secret_get)
- FastAPI HTTP API with multi-user Bearer token auth (API_KEYS JSON map)
- Regex-based credential detection with auto-redaction
- AES-256-GCM encryption fallback for non-Vault deployments
- Vault KV v2 client (stdlib urllib, K8s SA auto-auth)
- Per-user data isolation (all queries scoped by user_id)
- Secret migration endpoint for existing plain-text credentials
- Backward-compatible env var aliases (CLAUDE_MEMORY_API_URL)

Infrastructure:
- Docker + docker-compose (API + PostgreSQL + optional Vault)
- Woodpecker CI (test → build → push → kubectl deploy)
- GitHub Actions CI (Python 3.11/3.12/3.13) + Release (GHCR + PyPI)
- Helm chart + raw Kubernetes manifests

96 tests passing across 6 test files.
This commit is contained in:
Viktor Barzin 2026-03-14 09:42:05 +00:00
commit 0ed5e1e016
No known key found for this signature in database
GPG key ID: 0EB088298288D958
40 changed files with 3381 additions and 0 deletions

View file

@ -0,0 +1,6 @@
apiVersion: v2
name: claude-memory
description: Claude Memory MCP API server
type: application
version: 1.0.0
appVersion: "1.0.0"

View file

@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
app: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: {{ .Release.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
{{- range $key, $value := .Values.env }}
{{- if $value }}
env:
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}

View file

@ -0,0 +1,25 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: {{ .Values.ingress.className }}
tls:
- hosts:
- {{ .Values.ingress.host }}
secretName: {{ .Values.ingress.tls.secretName }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}
port:
number: {{ .Values.service.port }}
{{- end }}

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
selector:
app: {{ .Release.Name }}

View file

@ -0,0 +1,46 @@
replicaCount: 1
image:
repository: viktorbarzin/claude-memory-mcp
tag: latest
pullPolicy: Always
service:
type: ClusterIP
port: 80
targetPort: 8000
ingress:
enabled: true
className: nginx
host: claude-memory.example.com
tls:
secretName: tls-secret
resources:
requests:
memory: 32Mi
cpu: 10m
limits:
memory: 128Mi
env:
DATABASE_URL: ""
API_KEY: ""
# API_KEYS: '{}'
# VAULT_ADDR: ""
# VAULT_TOKEN: ""
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 3
periodSeconds: 10

View file

@ -0,0 +1,52 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: claude-memory
namespace: claude-memory
labels:
app: claude-memory
spec:
replicas: 1
selector:
matchLabels:
app: claude-memory
template:
metadata:
labels:
app: claude-memory
spec:
containers:
- name: claude-memory
image: viktorbarzin/claude-memory-mcp:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: claude-memory-secrets
key: database-url
- name: API_KEY
valueFrom:
secretKeyRef:
name: claude-memory-secrets
key: api-key
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 3
periodSeconds: 10
resources:
requests:
memory: 32Mi
cpu: 10m
limits:
memory: 128Mi

View file

@ -0,0 +1,24 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: claude-memory
namespace: claude-memory
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- claude-memory.example.com
secretName: tls-secret
rules:
- host: claude-memory.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: claude-memory
port:
number: 80

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: claude-memory

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: claude-memory
namespace: claude-memory
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8000
protocol: TCP
selector:
app: claude-memory