Add complete CI/CD pipeline for automated Astrolabe app releases: - GitHub Actions workflow for build, sign, and publish - Makefile for app store package creation - Version synchronization between info.xml and package.json - CHANGELOG.md with v0.1.0 release notes feat: configure commitizen monorepo with independent versioning Enable independent versioning for three components using scope-based commits: - MCP Server (feat(mcp) or unscoped): v* tags, updates pyproject.toml + Chart.yaml:appVersion - Helm Chart (feat(helm)): nextcloud-mcp-server-* tags, updates Chart.yaml:version - Astrolabe App (feat(astrolabe)): astrolabe-v* tags, updates info.xml + package.json Changes: - Add .cz.toml configs for Astrolabe and Helm chart - Update root pyproject.toml with scope filtering and tag ignores - Create bump helper scripts (bump-mcp.sh, bump-helm.sh, bump-astrolabe.sh) - Add CONTRIBUTING.md with version management documentation - Create component-specific changelogs - Configure appVersion/version separation for Helm chart This allows each component to release independently while maintaining proper version tracking and changelog generation.
Nextcloud MCP Server Helm Chart
This Helm chart deploys the Nextcloud MCP (Model Context Protocol) Server on a Kubernetes cluster, enabling AI assistants to interact with your Nextcloud instance.
Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- A running Nextcloud instance (accessible from the Kubernetes cluster)
- Nextcloud credentials (username/password for basic auth OR OAuth client for OAuth mode)
Installation
Quick Start with Basic Authentication
# Add the Helm repository
helm repo add nextcloud-mcp https://cbcoutinho.github.io/nextcloud-mcp-server
helm repo update
# Install with basic auth (recommended for most users)
helm install nextcloud-mcp nextcloud-mcp/nextcloud-mcp-server \
--set nextcloud.host=https://cloud.example.com \
--set auth.basic.username=myuser \
--set auth.basic.password=mypassword
Using a values file
Create a custom-values.yaml file:
nextcloud:
host: https://cloud.example.com
auth:
mode: basic
basic:
username: myuser
password: mypassword
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
Install with your custom values:
helm install nextcloud-mcp nextcloud-mcp/nextcloud-mcp-server -f custom-values.yaml
OAuth Authentication Mode (Experimental)
Warning: OAuth mode is experimental and requires patches to the Nextcloud user_oidc app. See the Authentication Guide for details.
nextcloud:
host: https://cloud.example.com
mcpServerUrl: https://mcp.example.com
publicIssuerUrl: https://cloud.example.com
auth:
mode: oauth
oauth:
# Optional: provide pre-registered client credentials
# If not provided, will use Dynamic Client Registration
clientId: "your-client-id"
clientSecret: "your-client-secret"
persistence:
enabled: true
size: 100Mi
ingress:
enabled: true
className: nginx
hosts:
- host: mcp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: nextcloud-mcp-tls
hosts:
- mcp.example.com
Configuration
Key Configuration Parameters
Nextcloud Connection
| Parameter | Description | Default |
|---|---|---|
nextcloud.host |
URL of your Nextcloud instance (required) | "" |
nextcloud.mcpServerUrl |
MCP server URL for OAuth callbacks (OAuth only, optional) | Smart default* |
nextcloud.publicIssuerUrl |
Public issuer URL for OAuth (OAuth only, optional) | Smart default** |
Smart Defaults:
*mcpServerUrl: If not set, automatically uses ingress host (if enabled) orhttp://localhost:8000(for port-forward setups)**publicIssuerUrl: If not set, automatically defaults tonextcloud.host(which works when both clients and MCP server access Nextcloud at the same URL)
Authentication
| Parameter | Description | Default |
|---|---|---|
auth.mode |
Authentication mode: basic or oauth |
basic |
auth.basic.username |
Nextcloud username (basic auth) | "" |
auth.basic.password |
Nextcloud password (basic auth) | "" |
auth.basic.existingSecret |
Use existing secret for credentials | "" |
auth.oauth.clientId |
OAuth client ID (OAuth mode, optional) | "" |
auth.oauth.clientSecret |
OAuth client secret (OAuth mode, optional) | "" |
auth.oauth.persistence.enabled |
Enable persistent storage for OAuth | true |
auth.oauth.persistence.size |
Size of OAuth storage PVC | 100Mi |
MCP Server Configuration
| Parameter | Description | Default |
|---|---|---|
mcp.transport |
Transport mode | streamable-http |
mcp.port |
Server port (used by both auth modes) | 8000 |
mcp.extraArgs |
Additional command-line arguments | [] |
The extraArgs parameter allows you to pass additional command-line arguments to the MCP server. This is useful for enabling debug logging, enabling specific apps, or other runtime configuration.
Example:
mcp:
extraArgs:
- "--log-level"
- "debug"
- "--enable-app"
- "notes"
Image Configuration
| Parameter | Description | Default |
|---|---|---|
image.repository |
Container image repository | ghcr.io/cbcoutinho/nextcloud-mcp-server |
image.pullPolicy |
Image pull policy | IfNotPresent |
Note: Image tag is automatically set to the chart's appVersion and cannot be overridden.
Resources
| Parameter | Description | Default |
|---|---|---|
resources.limits.cpu |
CPU limit | 1000m |
resources.limits.memory |
Memory limit | 512Mi |
resources.requests.cpu |
CPU request | 100m |
resources.requests.memory |
Memory request | 128Mi |
Service
| Parameter | Description | Default |
|---|---|---|
service.type |
Service type | ClusterIP |
service.port |
Service port | 8000 |
Ingress
| Parameter | Description | Default |
|---|---|---|
ingress.enabled |
Enable ingress | false |
ingress.className |
Ingress class name | "" |
ingress.hosts |
Ingress host configuration | See values.yaml |
ingress.tls |
Ingress TLS configuration | [] |
Autoscaling
| Parameter | Description | Default |
|---|---|---|
autoscaling.enabled |
Enable HPA | false |
autoscaling.minReplicas |
Minimum replicas | 1 |
autoscaling.maxReplicas |
Maximum replicas | 10 |
autoscaling.targetCPUUtilizationPercentage |
Target CPU % | 80 |
Health Probes
| Parameter | Description | Default |
|---|---|---|
livenessProbe.httpGet.path |
Liveness probe endpoint | /health/live |
livenessProbe.initialDelaySeconds |
Initial delay for liveness | 30 |
livenessProbe.periodSeconds |
Check interval for liveness | 10 |
readinessProbe.httpGet.path |
Readiness probe endpoint | /health/ready |
readinessProbe.initialDelaySeconds |
Initial delay for readiness | 10 |
readinessProbe.periodSeconds |
Check interval for readiness | 5 |
The application exposes HTTP health check endpoints:
/health/live- Liveness probe (checks if application is running)/health/ready- Readiness probe (checks if application is ready to serve traffic)
Document Processing (Optional)
| Parameter | Description | Default |
|---|---|---|
documentProcessing.enabled |
Enable document processing | false |
documentProcessing.defaultProcessor |
Default processor | unstructured |
documentProcessing.unstructured.enabled |
Enable Unstructured.io processor | false |
documentProcessing.unstructured.apiUrl |
Unstructured API URL | http://unstructured:8000 |
documentProcessing.tesseract.enabled |
Enable Tesseract OCR | false |
Vector Search & Semantic Capabilities (Optional)
Enable semantic search capabilities by deploying a vector database (Qdrant) and embedding service (Ollama or OpenAI).
Vector Sync Configuration:
| Parameter | Description | Default |
|---|---|---|
vectorSync.enabled |
Enable background vector synchronization | false |
vectorSync.scanInterval |
Scan interval in seconds | 3600 |
vectorSync.processorWorkers |
Number of concurrent processor workers | 3 |
vectorSync.queueMaxSize |
Maximum queue size for pending documents | 10000 |
Document Chunking Configuration:
| Parameter | Description | Default |
|---|---|---|
documentChunking.chunkSize |
Number of words per chunk for embedding | 512 |
documentChunking.chunkOverlap |
Number of overlapping words between chunks | 50 |
Chunking Strategy:
- Small chunks (256-384): Better precision for searches, more storage overhead
- Medium chunks (512-768): Balanced approach (recommended for most use cases)
- Large chunks (1024+): Better context preservation, less precise matching
- Overlap: Should be 10-20% of chunk size to preserve context across boundaries
Qdrant Vector Database:
Qdrant is deployed as a subchart when qdrant.enabled is true. All configuration values are passed through to the qdrant/qdrant chart.
| Parameter | Description | Default |
|---|---|---|
qdrant.enabled |
Deploy Qdrant as a subchart | false |
qdrant.replicaCount |
Number of Qdrant replicas | 1 |
qdrant.image.tag |
Qdrant version | v1.12.5 |
qdrant.apiKey |
Optional API key for authentication | "" |
qdrant.persistence.size |
Storage size for vector data | 10Gi |
qdrant.persistence.storageClass |
Storage class | "" |
qdrant.resources.requests.cpu |
CPU request | 200m |
qdrant.resources.requests.memory |
Memory request | 512Mi |
qdrant.resources.limits.cpu |
CPU limit | 1000m |
qdrant.resources.limits.memory |
Memory limit | 2Gi |
Ollama Embedding Service:
Ollama is deployed as a subchart when ollama.enabled is true. All configuration values are passed through to the ollama/ollama chart. Alternatively, set ollama.url to use an external Ollama instance.
| Parameter | Description | Default |
|---|---|---|
ollama.enabled |
Deploy Ollama as a subchart | false |
ollama.url |
External Ollama URL (use with enabled: false) |
"" |
ollama.embeddingModel |
Embedding model to use | nomic-embed-text |
ollama.verifySsl |
Verify SSL certificates | true |
ollama.replicaCount |
Number of Ollama replicas | 1 |
ollama.ollama.models.pull |
Models to pull on startup | ["nomic-embed-text"] |
ollama.persistentVolume.enabled |
Enable persistent storage | true |
ollama.persistentVolume.size |
Storage size for models | 20Gi |
ollama.resources.requests.cpu |
CPU request | 500m |
ollama.resources.requests.memory |
Memory request | 1Gi |
ollama.resources.limits.cpu |
CPU limit | 2000m |
ollama.resources.limits.memory |
Memory limit | 4Gi |
OpenAI Embedding Provider (Alternative):
Use OpenAI or any OpenAI-compatible API instead of Ollama.
| Parameter | Description | Default |
|---|---|---|
openai.enabled |
Enable OpenAI embedding provider | false |
openai.apiKey |
OpenAI API key | "" |
openai.existingSecret |
Use existing secret for API key | "" |
openai.secretKey |
Key in secret containing API key | api-key |
openai.baseUrl |
Custom API endpoint (optional) | "" |
Observability & Monitoring
The chart includes comprehensive observability features including Prometheus metrics, OpenTelemetry tracing, and Grafana dashboards.
Metrics Configuration:
| Parameter | Description | Default |
|---|---|---|
observability.metrics.enabled |
Enable Prometheus metrics | true |
observability.metrics.port |
Metrics port | 9090 |
observability.metrics.path |
Metrics endpoint path | /metrics |
Tracing Configuration:
| Parameter | Description | Default |
|---|---|---|
observability.tracing.enabled |
Enable OpenTelemetry tracing | false |
observability.tracing.endpoint |
OTLP collector endpoint | "" |
observability.tracing.serviceName |
Service name in traces | nextcloud-mcp-server |
observability.tracing.samplingRate |
Trace sampling rate (0.0-1.0) | 1.0 |
Logging Configuration:
| Parameter | Description | Default |
|---|---|---|
observability.logging.format |
Log format (json or text) | json |
observability.logging.level |
Log level | INFO |
observability.logging.includeTraceContext |
Include trace IDs in logs | true |
ServiceMonitor (Prometheus Operator):
| Parameter | Description | Default |
|---|---|---|
serviceMonitor.enabled |
Create ServiceMonitor resource | false |
serviceMonitor.interval |
Scrape interval | 30s |
serviceMonitor.scrapeTimeout |
Scrape timeout | 10s |
serviceMonitor.labels |
Additional labels for ServiceMonitor | {} |
PrometheusRule (Prometheus Operator):
| Parameter | Description | Default |
|---|---|---|
prometheusRule.enabled |
Create PrometheusRule with alert rules | false |
prometheusRule.labels |
Additional labels for PrometheusRule | {} |
Grafana Dashboards:
| Parameter | Description | Default |
|---|---|---|
dashboards.enabled |
Enable automatic dashboard provisioning | false |
dashboards.grafanaFolder |
Grafana folder name for dashboards | Nextcloud MCP |
dashboards.labels |
Additional labels for dashboard ConfigMap | {} |
dashboards.annotations |
Additional annotations for dashboard ConfigMap | {} |
When dashboards.enabled is true, a ConfigMap with the Grafana dashboard is created with the grafana_dashboard: "1" label. This enables automatic discovery by Grafana sidecar containers (commonly used with kube-prometheus-stack).
The dashboard provides comprehensive monitoring including:
- HTTP request metrics (RED pattern: Rate, Errors, Duration)
- MCP tool performance and errors
- Nextcloud API performance by app (notes, calendar, contacts, etc.)
- OAuth token operations and cache hit rates
- External dependency health (Nextcloud, Qdrant, Keycloak, Unstructured API)
- Vector sync processing pipeline (when enabled)
For manual import or more details, see charts/nextcloud-mcp-server/dashboards/README.md.
Examples
Example 1: Basic Auth with Ingress
nextcloud:
host: https://cloud.example.com
auth:
mode: basic
basic:
username: admin
password: secure-password
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: mcp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: mcp-tls
hosts:
- mcp.example.com
resources:
limits:
cpu: 2000m
memory: 1Gi
requests:
cpu: 200m
memory: 256Mi
Example 2: Using Existing Secrets
Basic Auth with Existing Secret
Create a secret manually:
kubectl create secret generic nextcloud-credentials \
--from-literal=username=myuser \
--from-literal=password=mypassword
Then reference it in your values:
nextcloud:
host: https://cloud.example.com
auth:
mode: basic
basic:
existingSecret: nextcloud-credentials
usernameKey: username
passwordKey: password
OAuth with Existing Secret (Pre-registered Client)
If you have a pre-registered OAuth client:
kubectl create secret generic nextcloud-oauth-creds \
--from-literal=clientId=my-oauth-client-id \
--from-literal=clientSecret=my-oauth-client-secret
Then reference it in your values:
nextcloud:
host: https://cloud.example.com
# mcpServerUrl and publicIssuerUrl are optional!
# If not set, mcpServerUrl defaults to ingress host or localhost
# publicIssuerUrl defaults to nextcloud.host
auth:
mode: oauth
oauth:
existingSecret: nextcloud-oauth-creds
clientIdKey: clientId
clientSecretKey: clientSecret
persistence:
enabled: true
ingress:
enabled: true
hosts:
- host: mcp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: mcp-tls
hosts:
- mcp.example.com
Example 3: OAuth with Document Processing and Dynamic Client Registration
This example shows OAuth without pre-registered credentials (using DCR) and optional URL values:
nextcloud:
host: https://cloud.example.com
# mcpServerUrl will automatically use ingress host (https://mcp.example.com)
# publicIssuerUrl will automatically default to nextcloud.host
auth:
mode: oauth
oauth:
# No clientId/clientSecret - will use Dynamic Client Registration!
persistence:
enabled: true
storageClass: fast-ssd
size: 200Mi
documentProcessing:
enabled: true
defaultProcessor: unstructured
unstructured:
enabled: true
apiUrl: http://unstructured-api:8000
strategy: hi_res
languages: eng,deu,fra
ingress:
enabled: true
className: nginx
hosts:
- host: mcp.example.com
paths:
- path: /
pathType: Prefix
Example 4: High Availability with Autoscaling
replicaCount: 2
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 20
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
resources:
limits:
cpu: 2000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- nextcloud-mcp-server
topologyKey: kubernetes.io/hostname
Example 5: Semantic Search with Qdrant and Ollama
Deploy with vector search capabilities using embedded Qdrant and Ollama:
nextcloud:
host: https://cloud.example.com
auth:
mode: basic
basic:
username: admin
password: secure-password
# Enable vector sync
vectorSync:
enabled: true
scanInterval: 1800 # Scan every 30 minutes
processorWorkers: 5
# Deploy Qdrant as a subchart
qdrant:
enabled: true
persistence:
size: 20Gi
storageClass: fast-ssd
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
# Deploy Ollama as a subchart
ollama:
enabled: true
embeddingModel: nomic-embed-text
persistentVolume:
size: 30Gi
storageClass: standard
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 4000m
memory: 8Gi
Or use an external Ollama instance:
vectorSync:
enabled: true
qdrant:
enabled: true
# Use external Ollama instead of deploying subchart
ollama:
enabled: false
url: "http://ollama.ai-services.svc.cluster.local:11434"
embeddingModel: nomic-embed-text
Or use OpenAI for embeddings:
vectorSync:
enabled: true
qdrant:
enabled: true
# Use OpenAI instead of Ollama
openai:
enabled: true
apiKey: "sk-..."
# Or use existing secret:
# existingSecret: openai-api-key
# secretKey: api-key
Upgrading
To upgrade an existing deployment:
# Update the repository
helm repo update
# Upgrade with your custom values
helm upgrade nextcloud-mcp nextcloud-mcp/nextcloud-mcp-server -f custom-values.yaml
To upgrade with new values:
helm upgrade nextcloud-mcp nextcloud-mcp/nextcloud-mcp-server \
--set resources.limits.memory=1Gi
Uninstalling
helm uninstall nextcloud-mcp
Note: This will delete all resources including PVCs. If you want to preserve OAuth client data, backup the PVC before uninstalling.
Troubleshooting
Check pod status
kubectl get pods -l app.kubernetes.io/name=nextcloud-mcp-server
View logs
kubectl logs -l app.kubernetes.io/name=nextcloud-mcp-server --tail=100 -f
Check health endpoints
The application exposes health check endpoints for monitoring:
# Port forward to the service
kubectl port-forward svc/nextcloud-mcp 8000:8000
# Check liveness (if app is running)
curl http://localhost:8000/health/live
# Check readiness (if app is ready to serve traffic)
curl http://localhost:8000/health/ready
Example responses:
Liveness (always returns 200 if running):
{
"status": "alive",
"mode": "basic"
}
Readiness (returns 200 if ready, 503 if not ready):
{
"status": "ready",
"checks": {
"nextcloud_configured": "ok",
"auth_mode": "basic",
"auth_configured": "ok"
}
}
Common Issues
-
Connection refused to Nextcloud
- Verify
nextcloud.hostis accessible from the Kubernetes cluster - Check network policies and firewall rules
- Verify
-
Authentication failures
- For basic auth: verify username/password are correct
- For OAuth: check that OIDC app is properly configured
-
OAuth persistence issues
- Verify PVC is bound:
kubectl get pvc - Check storage class exists:
kubectl get storageclass
- Verify PVC is bound:
-
Resource constraints
- Increase memory limits if seeing OOM errors
- Adjust CPU requests based on load
Security Considerations
- Secrets Management: Consider using external secret management (e.g., Sealed Secrets, External Secrets Operator)
- TLS: Always use TLS/HTTPS for production deployments
- Network Policies: Restrict network access to necessary services only
- RBAC: Review and customize ServiceAccount permissions as needed
- App Passwords: For basic auth, use Nextcloud app passwords instead of main account passwords
Support
- GitHub Issues: https://github.com/cbcoutinho/nextcloud-mcp-server/issues
- Documentation: https://github.com/cbcoutinho/nextcloud-mcp-server#readme
License
This chart is licensed under AGPL-3.0, consistent with the Nextcloud MCP Server project.