chore: remove helm chart (migrated to cbcoutinho/helm-charts)

The helm chart has been migrated to a dedicated repository at
https://github.com/cbcoutinho/helm-charts. This removes the chart
source, release workflow, bump script, and updates all documentation
to point to the new repository.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-04-07 16:31:35 +02:00
co-authored by Claude Opus 4.6
parent 010c598b24
commit c4b74e7e20
33 changed files with 41 additions and 6273 deletions
+20 -97
View File
@@ -9,7 +9,7 @@ jobs:
bump-version: bump-version:
if: "!startsWith(github.event.head_commit.message, 'bump:') && !startsWith(github.event.head_commit.message, 'chore(release):')" if: "!startsWith(github.event.head_commit.message, 'bump:') && !startsWith(github.event.head_commit.message, 'chore(release):')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "Bump version and create changelog for monorepo components" name: "Bump version and create changelog"
permissions: permissions:
contents: write contents: write
packages: write packages: write
@@ -35,94 +35,33 @@ jobs:
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Detect and bump component versions - name: Detect and bump version
id: bump id: bump
run: | run: |
set -euo pipefail set -euo pipefail
# Track which components were bumped echo "Checking for version bump..."
BUMPED_COMPONENTS=""
# Helper function to check for commits with specific scope since last tag # Get the most recent tag
has_commits_since_tag() { last_tag=$(git tag --sort=-creatordate | grep -E "^v[0-9]" | head -n 1 || echo "")
local tag_pattern="$1"
local scope_pattern="$2"
# Get the most recent tag matching the pattern if [ -z "$last_tag" ]; then
local last_tag=$(git tag --sort=-creatordate | grep -E "^${tag_pattern}" | head -n 1 || echo "")
if [ -z "$last_tag" ]; then
# No previous tag, check all commits on master
local commit_range="master"
else
# Check commits since last tag
local commit_range="${last_tag}..HEAD"
fi
# Count commits matching the scope pattern
local commit_count=$(git log "$commit_range" --oneline --grep="^${scope_pattern}" -E | wc -l)
if [ "$commit_count" -gt 0 ]; then
echo "Found $commit_count commits for scope '$scope_pattern' since $last_tag"
return 0
else
echo "No commits found for scope '$scope_pattern' since $last_tag"
return 1
fi
}
# Bump MCP server (default - all commits except helm scope)
echo "Checking MCP server for version bump..."
# Get the most recent MCP tag
last_mcp_tag=$(git tag --sort=-creatordate | grep -E "^v[0-9]" | head -n 1 || echo "")
if [ -z "$last_mcp_tag" ]; then
commit_range="master" commit_range="master"
else else
commit_range="${last_mcp_tag}..HEAD" commit_range="${last_tag}..HEAD"
fi fi
# Count conventional commits that are NOT scoped to helm # Count conventional commits
mcp_commit_count=$(git log "$commit_range" --oneline --grep="^(feat|fix|docs|refactor|perf|test|build|ci|chore)" -E | \ commit_count=$(git log "$commit_range" --oneline --grep="^(feat|fix|docs|refactor|perf|test|build|ci|chore)" -E | wc -l)
{ grep -v "(helm)" || true; } | wc -l)
MCP_BUMPED=false if [ "$commit_count" -gt 0 ]; then
if [ "$mcp_commit_count" -gt 0 ]; then echo "Found $commit_count commits since $last_tag"
echo "Found $mcp_commit_count commits for MCP server since $last_mcp_tag" echo "Bumping version..."
echo "Bumping MCP server version..."
./scripts/bump-mcp.sh ./scripts/bump-mcp.sh
BUMPED_COMPONENTS="$BUMPED_COMPONENTS mcp"
MCP_BUMPED=true
else
echo "No commits found for MCP server since $last_mcp_tag"
fi
# Bump Helm chart (scope: helm OR when MCP appVersion changes)
echo "Checking Helm chart for version bump..."
HELM_HAS_COMMITS=false
if has_commits_since_tag "nextcloud-mcp-server-" "(feat|fix|docs|refactor|perf|test|build|ci|chore)\(helm\)(!)?:"; then
HELM_HAS_COMMITS=true
fi
if [ "$HELM_HAS_COMMITS" = true ]; then
echo "Bumping Helm chart version (helm-scoped commits)..."
./scripts/bump-helm.sh
BUMPED_COMPONENTS="$BUMPED_COMPONENTS helm"
elif [ "$MCP_BUMPED" = true ]; then
echo "Bumping Helm chart version (appVersion changed)..."
./scripts/bump-helm.sh --increment PATCH
BUMPED_COMPONENTS="$BUMPED_COMPONENTS helm"
fi
# Output summary
if [ -z "$BUMPED_COMPONENTS" ]; then
echo "No components required version bumps"
echo "bumped=false" >> $GITHUB_OUTPUT
else
echo "Bumped components:$BUMPED_COMPONENTS"
echo "bumped=true" >> $GITHUB_OUTPUT echo "bumped=true" >> $GITHUB_OUTPUT
echo "components=$BUMPED_COMPONENTS" >> $GITHUB_OUTPUT else
echo "No commits found since $last_tag"
echo "bumped=false" >> $GITHUB_OUTPUT
fi fi
- name: Push tags - name: Push tags
@@ -130,35 +69,19 @@ jobs:
run: | run: |
git push git push
git push --tags git push --tags
echo "Pushed tags for components:${{ steps.bump.outputs.components }}" echo "Pushed version tags"
- name: Summary - name: Summary
run: | run: |
if [ "${{ steps.bump.outputs.bumped }}" == "true" ]; then if [ "${{ steps.bump.outputs.bumped }}" == "true" ]; then
tag=$(git tag --sort=-creatordate | grep -E '^v[0-9]' | head -n 1)
echo "## Version Bump Summary" >> $GITHUB_STEP_SUMMARY echo "## Version Bump Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "The following components were bumped:" >> $GITHUB_STEP_SUMMARY echo "- **MCP Server**: \`$tag\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "Tag has been pushed and release workflows will trigger automatically." >> $GITHUB_STEP_SUMMARY
for component in ${{ steps.bump.outputs.components }}; do
case $component in
mcp)
tag=$(git tag --sort=-creatordate | grep -E '^v[0-9]' | head -n 1)
echo "- **MCP Server**: \`$tag\`" >> $GITHUB_STEP_SUMMARY
;;
helm)
tag=$(git tag --sort=-creatordate | grep -E '^nextcloud-mcp-server-' | head -n 1)
echo "- **Helm Chart**: \`$tag\`" >> $GITHUB_STEP_SUMMARY
;;
esac
done
echo "" >> $GITHUB_STEP_SUMMARY
echo "Tags have been pushed and release workflows will trigger automatically." >> $GITHUB_STEP_SUMMARY
else else
echo "## Version Bump Summary" >> $GITHUB_STEP_SUMMARY echo "## Version Bump Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "No version bumps required - no relevant commits found since last release." >> $GITHUB_STEP_SUMMARY echo "No version bump required - no relevant commits found since last release." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The workflow completed successfully with no changes." >> $GITHUB_STEP_SUMMARY
fi fi
-137
View File
@@ -1,137 +0,0 @@
name: Release Charts
on:
push:
tags:
- v*
- nextcloud-mcp-server-*
jobs:
release:
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
with:
version: v3.16.0
- name: Add Helm repositories and update dependencies
run: |
helm repo add qdrant https://qdrant.github.io/qdrant-helm
helm repo add ollama https://otwld.github.io/ollama-helm
helm repo update
helm dependency build charts/nextcloud-mcp-server
- name: Run chart-releaser
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
with:
skip_existing: true
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Update gh-pages with Chart README and Index
run: |
# Get the repository name
REPO_NAME="${GITHUB_REPOSITORY##*/}"
REPO_OWNER="${GITHUB_REPOSITORY%/*}"
# Switch to gh-pages branch
git fetch origin gh-pages
git checkout gh-pages
# Copy Chart README to root
git checkout ${GITHUB_REF#refs/tags/} -- charts/nextcloud-mcp-server/README.md
mv charts/nextcloud-mcp-server/README.md README.md || true
rm -rf charts 2>/dev/null || true
# Create index.html with installation instructions
cat > index.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nextcloud MCP Server Helm Chart</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
line-height: 1.6;
}
code {
background: #f4f4f4;
padding: 2px 6px;
border-radius: 3px;
font-family: "Monaco", "Courier New", monospace;
}
pre {
background: #f4f4f4;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
}
h1, h2 { color: #0082c9; }
a { color: #0082c9; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>Nextcloud MCP Server Helm Chart</h1>
<p>A Helm chart for deploying the Nextcloud MCP (Model Context Protocol) Server on Kubernetes, enabling AI assistants to interact with your Nextcloud instance.</p>
<h2>Installation</h2>
<p>Add the Helm repository:</p>
<pre><code>helm repo add nextcloud-mcp https://REPO_OWNER.github.io/REPO_NAME/
helm repo update</code></pre>
<p>Install the chart:</p>
<pre><code>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</code></pre>
<h2>Documentation</h2>
<ul>
<li><a href="README.md">Chart README</a> - Full documentation for the Helm chart</li>
<li><a href="https://github.com/REPO_OWNER/REPO_NAME">GitHub Repository</a> - Source code and issues</li>
<li><a href="index.yaml">Helm Repository Index</a> - Chart metadata</li>
</ul>
<h2>Quick Start</h2>
<p>See the <a href="README.md">full documentation</a> for detailed configuration options, examples, and troubleshooting guides.</p>
<hr>
<p><small>Generated by <a href="https://github.com/helm/chart-releaser">chart-releaser</a></small></p>
</body>
</html>
EOF
# Replace placeholders
sed -i "s/REPO_OWNER/$REPO_OWNER/g" index.html
sed -i "s/REPO_NAME/$REPO_NAME/g" index.html
# Commit changes
git add README.md index.html
git commit -m "Update README and index from chart release" || echo "No changes to commit"
git push origin gh-pages
+10 -61
View File
@@ -2,58 +2,35 @@
## Version Management ## Version Management
This monorepo uses commitizen for version management with **independent versioning** for two components: This project uses [commitizen](https://commitizen-tools.github.io/commitizen/) for version management following PEP 440 (`major_version_zero = true`, 0.x.x for pre-1.0).
### Components > **Note:** The Helm chart has been moved to [cbcoutinho/helm-charts](https://github.com/cbcoutinho/helm-charts). The Astrolabe Nextcloud app has been moved to [cbcoutinho/astrolabe](https://github.com/cbcoutinho/astrolabe).
| Component | Scope | Bump Command | Tag Example |
|-----------|-------|--------------|-------------|
| MCP Server | `mcp` or none | `./scripts/bump-mcp.sh` | `v0.54.0` |
| Helm Chart | `helm` | `./scripts/bump-helm.sh` | `nextcloud-mcp-server-0.54.0` |
> **Note:** The Astrolabe Nextcloud app has been moved to its own repository at [cbcoutinho/astrolabe](https://github.com/cbcoutinho/astrolabe).
### Commit Message Format ### Commit Message Format
Use conventional commits with **scopes** to target specific components: Use [conventional commits](https://www.conventionalcommits.org/):
```bash ```bash
# MCP server changes feat: add new feature
feat(mcp): add calendar sync API feat(mcp): add calendar sync API
fix(mcp): resolve authentication bug fix: resolve authentication bug
docs: update README
# Helm chart changes
feat(helm): add resource limits
docs(helm): update values documentation
```
**Unscoped commits** default to the MCP server:
```bash
feat: add new feature # → MCP server (v0.54.0)
``` ```
### Release Workflow ### Release Workflow
#### 1. Make Changes with Scoped Commits #### 1. Make Changes with Conventional Commits
```bash ```bash
git commit -m "feat(helm): add ingress annotations" git commit -m "feat: add calendar sync"
git commit -m "feat(mcp): add calendar sync"
``` ```
#### 2. Bump Component Versions #### 2. Bump Version
```bash ```bash
# Bump MCP server (reads commits with scope=mcp or unscoped)
./scripts/bump-mcp.sh ./scripts/bump-mcp.sh
# → Creates tag: v0.54.0 # → Creates tag: v0.54.0
# → Updates: pyproject.toml, Chart.yaml:appVersion # → Updates: pyproject.toml
# Bump Helm chart (reads commits with scope=helm)
./scripts/bump-helm.sh
# → Creates tag: nextcloud-mcp-server-0.54.0
# → Updates: Chart.yaml:version
``` ```
#### 3. Push Tags #### 3. Push Tags
@@ -62,13 +39,6 @@ git commit -m "feat(mcp): add calendar sync"
git push --follow-tags git push --follow-tags
``` ```
### Changelog Filtering
Each component maintains its own `CHANGELOG.md`:
- **MCP Server**: `CHANGELOG.md` (root) - includes `feat(mcp):` and unscoped commits
- **Helm Chart**: `charts/nextcloud-mcp-server/CHANGELOG.md` - includes `feat(helm):` only
### Manual Version Bumps ### Manual Version Bumps
For specific increments: For specific increments:
@@ -82,25 +52,4 @@ uv run cz bump --increment MINOR
# Major bump (0.53.0 → 1.0.0) # Major bump (0.53.0 → 1.0.0)
uv run cz bump --increment MAJOR uv run cz bump --increment MAJOR
# For non-MCP components, use --config
cd charts/nextcloud-mcp-server
uv run cz --config .cz.toml bump --increment MINOR
``` ```
### Versioning Philosophy
- **MCP Server**: Follows PEP 440, `major_version_zero = true` (0.x.x for pre-1.0)
- **Helm Chart**: Follows PEP 440, starts at 0.53.0 (continues from current)
### Chart.yaml Version vs appVersion
The Helm chart has TWO version fields:
- **`version`**: Chart packaging version (bumped by `feat(helm):`)
- Example: `0.53.0``0.54.0` when adding resource limits
- **`appVersion`**: MCP server version being deployed (bumped by `feat(mcp):`)
- Example: `"0.53.0"``"0.54.0"` when MCP server releases
This allows the chart to evolve independently from the application.
+3 -3
View File
@@ -52,7 +52,7 @@ docker compose --profile login-flow up -d # Port 8004
**Next Steps:** **Next Steps:**
- Connect your MCP client (Claude Desktop, IDEs, `mcp dev`, etc.) - Connect your MCP client (Claude Desktop, IDEs, `mcp dev`, etc.)
- See [docs/installation.md](docs/installation.md) for other deployment options (local, Kubernetes) - See [docs/installation.md](docs/installation.md) for other deployment options. For Kubernetes (Helm), see [cbcoutinho/helm-charts](https://github.com/cbcoutinho/helm-charts)
## Key Features ## Key Features
@@ -60,7 +60,7 @@ docker compose --profile login-flow up -d # Port 8004
- **MCP Resources** - Structured data URIs for browsing Nextcloud data - **MCP Resources** - Structured data URIs for browsing Nextcloud data
- **Semantic Search (Experimental)** - Optional vector-powered search for Notes, Files, News items, and Deck cards (requires Qdrant + Ollama) - **Semantic Search (Experimental)** - Optional vector-powered search for Notes, Files, News items, and Deck cards (requires Qdrant + Ollama)
- **Document Processing** - OCR and text extraction from PDFs, DOCX, images with progress notifications - **Document Processing** - OCR and text extraction from PDFs, DOCX, images with progress notifications
- **Flexible Deployment** - Docker, Kubernetes (Helm), VM, or local installation - **Flexible Deployment** - Docker, Kubernetes ([Helm chart](https://github.com/cbcoutinho/helm-charts)), VM, or local installation
- **Production-Ready Auth** - Basic Auth with app passwords (recommended) or OAuth2/OIDC (experimental) - **Production-Ready Auth** - Basic Auth with app passwords (recommended) or OAuth2/OIDC (experimental)
- **Multiple Transports** - SSE, HTTP, and streamable-http support - **Multiple Transports** - SSE, HTTP, and streamable-http support
@@ -146,7 +146,7 @@ This enables natural language queries and helps discover related content across
## Documentation ## Documentation
### Getting Started ### Getting Started
- **[Installation](docs/installation.md)** - Docker, Kubernetes, local, or VM deployment - **[Installation](docs/installation.md)** - Docker, local, or VM deployment. [Helm chart](https://github.com/cbcoutinho/helm-charts) for Kubernetes
- **[Configuration](docs/configuration.md)** - Environment variables and advanced options - **[Configuration](docs/configuration.md)** - Environment variables and advanced options
- **[Authentication](docs/authentication.md)** - Basic Auth vs OAuth2/OIDC setup - **[Authentication](docs/authentication.md)** - Basic Auth vs OAuth2/OIDC setup
- **[Running the Server](docs/running.md)** - Start, manage, and troubleshoot - **[Running the Server](docs/running.md)** - Start, manage, and troubleshoot
-25
View File
@@ -1,25 +0,0 @@
[tool.commitizen]
name = "cz_conventional_commits"
version = "0.58.31"
tag_format = "nextcloud-mcp-server-$version"
version_scheme = "semver"
update_changelog_on_bump = true
major_version_zero = true
# Update chart version only (NOT appVersion)
version_files = [
"Chart.yaml:^version:"
]
# Ignore tags from other components
ignored_tag_formats = [
"v*", # MCP server tags
"astrolabe-v*", # Astrolabe tags
]
# Filter commits by scope
# Includes helm-scoped commits AND MCP server version bumps (which update appVersion)
[tool.commitizen.customize]
changelog_pattern = "^((feat|fix|docs|refactor|perf|test|build|ci|chore)\\(helm\\)(!)?:|bump: version.*→.*)"
schema_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)\\(helm\\)(!)?:\\s.+"
message_template = "{{change_type}}(helm): {{message}}"
-1
View File
@@ -1 +0,0 @@
charts/
-23
View File
@@ -1,23 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
dependencies:
- name: qdrant
repository: https://qdrant.github.io/qdrant-helm
version: 1.17.1
- name: ollama
repository: https://otwld.github.io/ollama-helm
version: 1.47.0
digest: sha256:92b4741d6c8c9ef2303d179074335952e2867b8e2ace8ab74f2ab912e06f5d1c
generated: "2026-03-27T15:36:24.829508269Z"
-45
View File
@@ -1,45 +0,0 @@
apiVersion: v2
name: nextcloud-mcp-server
description: A Helm chart for Nextcloud MCP Server - enables AI assistants to interact with Nextcloud
type: application
version: 0.58.31
appVersion: "0.68.4"
keywords:
- nextcloud
- mcp
- model-context-protocol
- llm
- ai
- claude
- webdav
- caldav
- carddav
maintainers:
- name: Chris Coutinho
email: chris@coutinho.io
home: https://github.com/cbcoutinho/nextcloud-mcp-server
sources:
- https://github.com/cbcoutinho/nextcloud-mcp-server
icon: https://raw.githubusercontent.com/nextcloud/server/master/core/img/logo/logo.svg
annotations:
# Grafana dashboard support
grafana_dashboard: "true"
grafana_dashboard_folder: "Nextcloud MCP"
artifacthub.io/changes: |
- kind: added
description: Login Flow v2 auth mode for Helm chart (ADR-022)
- kind: added
description: Multi-user BasicAuth guidance in post-install NOTES
- kind: added
description: Version and changelog info in post-install NOTES
- kind: changed
description: Updated appVersion to 0.64.4
dependencies:
- name: qdrant
version: "1.17.1"
repository: https://qdrant.github.io/qdrant-helm
condition: qdrant.networkMode.deploySubchart
- name: ollama
version: "1.47.0"
repository: https://otwld.github.io/ollama-helm
condition: ollama.enabled
-742
View File
@@ -1,742 +0,0 @@
# 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
```bash
# 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:
```yaml
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:
```bash
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](https://github.com/cbcoutinho/nextcloud-mcp-server#authentication) for details.
```yaml
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 URL for browser-accessible OAuth authorization endpoint (OAuth only, optional) | Smart default** |
**Smart Defaults:**
- `*mcpServerUrl`: If not set, automatically uses ingress host (if enabled) or `http://localhost:8000` (for port-forward setups)
- `**publicIssuerUrl`: If not set, defaults to `nextcloud.host`. **Only used for authorization endpoints** that browsers must access. All server-to-server endpoints (token, JWKS, introspection, userinfo) use URLs from OIDC discovery without rewriting
#### 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` |
#### Data Storage
The `/app/data` directory is used for application data (token databases, Qdrant persistent storage, etc.). It is always mounted as writable to support the read-only root filesystem security context.
| Parameter | Description | Default |
|-----------|-------------|---------|
| `dataStorage.enabled` | Enable persistent storage for `/app/data` | `false` |
| `dataStorage.size` | Size of data storage PVC | `1Gi` |
| `dataStorage.storageClass` | Storage class (leave empty for default) | `""` |
| `dataStorage.accessMode` | Access mode | `ReadWriteOnce` |
| `dataStorage.existingClaim` | Use existing PVC | `""` |
**When to enable persistence:**
- Multi-user basic auth with offline access (stores `tokens.db`)
- Qdrant persistent mode (stores vector database)
- Any feature requiring persistent app data
**When persistence is disabled:** Uses `emptyDir` (non-persistent, data lost on pod restart, but directory remains writable).
#### 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:**
```yaml
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 with BM25 hybrid search by deploying a vector database (Qdrant) and embedding service (Ollama or OpenAI).
**Semantic Search Configuration:**
| Parameter | Description | Default |
|-----------|-------------|---------|
| `semanticSearch.enabled` | Enable semantic search and background vector synchronization | `false` |
| `semanticSearch.scanInterval` | Scan interval in seconds | `3600` |
| `semanticSearch.processorWorkers` | Number of concurrent processor workers | `3` |
| `semanticSearch.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](https://github.com/qdrant/qdrant-helm) 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](https://github.com/otwld/ollama-helm) 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
```yaml
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:
```bash
kubectl create secret generic nextcloud-credentials \
--from-literal=username=myuser \
--from-literal=password=mypassword
```
Then reference it in your values:
```yaml
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:
```bash
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:
```yaml
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 (only used for browser-accessible auth endpoint)
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:
```yaml
nextcloud:
host: https://cloud.example.com
# mcpServerUrl will automatically use ingress host (https://mcp.example.com)
# publicIssuerUrl will automatically default to nextcloud.host (only used for browser-accessible auth endpoint)
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
```yaml
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:
```yaml
nextcloud:
host: https://cloud.example.com
auth:
mode: basic
basic:
username: admin
password: secure-password
# Enable semantic search
semanticSearch:
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:
```yaml
semanticSearch:
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:
```yaml
semanticSearch:
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:
```bash
# 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:
```bash
helm upgrade nextcloud-mcp nextcloud-mcp/nextcloud-mcp-server \
--set resources.limits.memory=1Gi
```
## Uninstalling
```bash
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
```bash
kubectl get pods -l app.kubernetes.io/name=nextcloud-mcp-server
```
### View logs
```bash
kubectl logs -l app.kubernetes.io/name=nextcloud-mcp-server --tail=100 -f
```
### Check health endpoints
The application exposes health check endpoints for monitoring:
```bash
# 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):
```json
{
"status": "alive",
"mode": "basic"
}
```
Readiness (returns 200 if ready, 503 if not ready):
```json
{
"status": "ready",
"checks": {
"nextcloud_configured": "ok",
"auth_mode": "basic",
"auth_configured": "ok"
}
}
```
### Common Issues
1. **Connection refused to Nextcloud**
- Verify `nextcloud.host` is accessible from the Kubernetes cluster
- For OAuth mode: Ensure MCP server can reach OIDC discovery endpoints (token, JWKS, introspection, userinfo URLs)
- Check network policies and firewall rules
- Note: Do not use internal Docker hostnames (like `http://app:80`) for `nextcloud.host` - use externally resolvable URLs
2. **Authentication failures**
- For basic auth: verify username/password are correct
- For OAuth: check that OIDC app is properly configured
3. **OAuth persistence issues**
- Verify PVC is bound: `kubectl get pvc`
- Check storage class exists: `kubectl get storageclass`
4. **Resource constraints**
- Increase memory limits if seeing OOM errors
- Adjust CPU requests based on load
## Security Considerations
1. **Secrets Management**: Consider using external secret management (e.g., Sealed Secrets, External Secrets Operator)
2. **TLS**: Always use TLS/HTTPS for production deployments
3. **Network Policies**: Restrict network access to necessary services only
4. **RBAC**: Review and customize ServiceAccount permissions as needed
5. **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.
@@ -1,161 +0,0 @@
# Grafana Dashboards
This directory contains example Grafana dashboards for monitoring the Nextcloud MCP Server.
## Dashboards
### nextcloud-mcp-server.json
All-in-one Operations Dashboard with comprehensive monitoring across all system components.
#### Overview Row
High-level metrics for quick health assessment:
- **Request Rate** (stat): Total requests per second
- **Error Rate** (stat): Percentage of 5xx errors with color thresholds
- **P95 Latency** (stat): 95th percentile request latency
- **Active Requests** (stat): Current in-flight requests
#### HTTP Metrics (RED Pattern)
Core request/error/duration metrics:
- **Request Rate by Endpoint** (timeseries): RPS breakdown by endpoint
- **Error Rate by Status Code** (timeseries): Error rates for 4xx/5xx codes
- **Latency Percentiles** (timeseries): P50, P95, P99 latency trends
- **Status Code Distribution** (piechart): Percentage breakdown of all status codes
#### MCP Tools Row
MCP-specific tool performance:
- **Top Tools by Call Volume** (bargauge): Top 10 most-called tools
- **Tool Error Rate** (timeseries): Error rates per tool
- **Tool Execution Duration** (timeseries): P95 latency by tool
#### Nextcloud API Row
Backend API performance metrics:
- **API Calls by App** (timeseries): Request rate per Nextcloud app (notes, calendar, contacts, etc.)
- **API Latency by App** (timeseries): P95 latency per app
- **API Retries by Reason** (timeseries): Retry patterns (429, timeout, connection errors)
- **API Error Rate** (stat): Overall API error percentage
#### OAuth & Authentication Row
OAuth token operations and caching:
- **Token Validations** (timeseries): Success/failure rates for token validation
- **Token Exchange Operations** (timeseries): RFC 8693 token exchange operations
- **Token Cache Hit Rate** (stat): Percentage of cache hits (color-coded: red<50%, yellow<80%, green≥80%)
- **Refresh Token Operations** (timeseries): Refresh token storage operations by type
#### Dependencies & Health Row
External dependency status monitoring:
- **Nextcloud Health** (stat): UP/DOWN status with color coding
- **Qdrant Health** (stat): Vector database health status
- **Keycloak Health** (stat): Identity provider health status
- **Unstructured API Health** (stat): Document processing API status
- **Health Check Duration** (timeseries): Health check latency by dependency
- **Database Operation Latency** (timeseries): P95 latency for DB operations (SQLite, Qdrant)
#### Vector Sync Row (when enabled)
Document processing pipeline metrics:
- **Documents Processed Rate** (timeseries): Processing throughput by status (success/failure)
- **Processing Queue Depth** (gauge): Current queue size with thresholds (yellow>50, red>100)
- **Qdrant Operations** (timeseries): Vector database operations by type
- **Document Processing Duration** (timeseries): P95 processing latency
## Importing to Grafana
### Manual Import
1. Open Grafana UI
2. Navigate to Dashboards → Import
3. Upload `nextcloud-mcp-server.json`
4. Select your Prometheus data source
5. Click "Import"
### Automated Import (Helm Chart)
The Helm chart now supports automatic dashboard provisioning via Grafana sidecar pattern.
#### Option 1: Using Helm Chart (Recommended)
Enable dashboard provisioning in your Helm values:
```yaml
# values.yaml for nextcloud-mcp-server chart
dashboards:
enabled: true
grafanaFolder: "Nextcloud MCP" # Folder name in Grafana
labels: {} # Additional labels if needed
```
Then deploy or upgrade:
```bash
helm upgrade --install nextcloud-mcp nextcloud-mcp-server \
--set dashboards.enabled=true
```
The dashboard will be automatically imported by Grafana if the sidecar is configured
to watch for ConfigMaps with label `grafana_dashboard: "1"`.
#### Option 2: Using kube-prometheus-stack
If using kube-prometheus-stack with Grafana sidecar enabled, the dashboard will be
automatically discovered and imported. Ensure your Grafana deployment has:
```yaml
# kube-prometheus-stack values
grafana:
sidecar:
dashboards:
enabled: true
label: grafana_dashboard
folder: /tmp/dashboards
provider:
foldersFromFilesStructure: true
```
#### Option 3: Manual ConfigMap Creation
For other Grafana setups, create a ConfigMap manually:
```bash
kubectl create configmap nextcloud-mcp-dashboard \
--from-file=nextcloud-mcp-server.json \
-n monitoring
# Add sidecar discovery label
kubectl label configmap nextcloud-mcp-dashboard \
grafana_dashboard=1 \
-n monitoring
# Add folder annotation (annotations support spaces, unlike labels)
kubectl annotate configmap nextcloud-mcp-dashboard \
grafana_folder="Nextcloud MCP" \
-n monitoring
```
## Dashboard Variables
The dashboard includes four template variables for dynamic filtering:
- **datasource**: Select your Prometheus data source
- **namespace**: Filter metrics by Kubernetes namespace (supports "All")
- **pod**: Filter by specific pod(s) - multi-select enabled (supports "All")
- **interval**: Query interval for rate calculations (1m, 5m, 10m, 30m, 1h - default: 5m)
## Customization
You can customize the dashboard by:
1. Adjusting refresh rate (default: 30s)
2. Modifying time range (default: last 6 hours)
3. Adding new panels for specific metrics
4. Adjusting thresholds in existing panels
## Metrics Reference
All metrics are documented in `/docs/observability.md`. Key metric prefixes:
- `mcp_http_*` - HTTP server metrics
- `mcp_tool_*` - MCP tool invocation metrics
- `mcp_nextcloud_api_*` - Nextcloud API call metrics
- `mcp_oauth_*` - OAuth token validation metrics
- `mcp_vector_sync_*` - Vector database sync metrics
- `mcp_db_*` - Database operation metrics
File diff suppressed because it is too large Load Diff
@@ -1,208 +0,0 @@
Thank you for installing {{ .Chart.Name }}!
Your Nextcloud MCP Server has been deployed in {{ .Values.auth.mode }} authentication mode.
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nextcloud-mcp-server.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nextcloud-mcp-server.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nextcloud-mcp-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nextcloud-mcp-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your MCP server"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
2. Check the deployment status:
kubectl --namespace {{ .Release.Namespace }} get pods -l "app.kubernetes.io/name={{ include "nextcloud-mcp-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}"
{{- if eq .Values.auth.mode "basic" }}
3. Basic Authentication Mode:
{{- if .Values.auth.basic.existingSecret }}
- Credentials: (using existing secret {{ .Values.auth.basic.existingSecret }})
{{- else }}
- Username: {{ .Values.auth.basic.username }}
- Password: (stored in secret {{ include "nextcloud-mcp-server.basicAuthSecretName" . }})
{{- end }}
- Connected to: {{ .Values.nextcloud.host }}
{{- else if eq .Values.auth.mode "oauth" }}
3. OAuth Authentication Mode:
- Server URL: {{ include "nextcloud-mcp-server.mcpServerUrl" . }}
- Issuer URL: {{ include "nextcloud-mcp-server.publicIssuerUrl" . }}
- Connected to: {{ .Values.nextcloud.host }}
{{- if .Values.auth.oauth.existingSecret }}
- Using existing OAuth client secret: {{ .Values.auth.oauth.existingSecret }}
{{- else if and .Values.auth.oauth.clientId .Values.auth.oauth.clientSecret }}
- Using pre-registered OAuth client
{{- else }}
- Using Dynamic Client Registration (DCR)
{{- end }}
{{- if .Values.auth.oauth.persistence.enabled }}
- OAuth client credentials are persisted in PVC: {{ include "nextcloud-mcp-server.oauthPvcName" . }}
{{- end }}
IMPORTANT: OAuth mode is experimental and requires patches to the user_oidc app.
See: https://github.com/cbcoutinho/nextcloud-mcp-server#authentication
{{- else if eq .Values.auth.mode "multi-user-basic" }}
3. Multi-User BasicAuth Mode (Pass-Through):
- Users provide credentials via Authorization header
- Connected to: {{ .Values.nextcloud.host }}
{{- if .Values.auth.multiUserBasic.enableOfflineAccess }}
- Offline access: Enabled (background operations with app passwords)
- Token storage: {{ .Values.auth.multiUserBasic.tokenStorageDb }}
{{- else }}
- Offline access: Disabled (stateless pass-through)
{{- end }}
{{- else if eq .Values.auth.mode "login-flow" }}
3. Login Flow v2 Mode (Experimental, ADR-022):
- Server URL: {{ include "nextcloud-mcp-server.mcpServerUrl" . }}
- Connected to: {{ .Values.nextcloud.host }}
- Token storage: {{ .Values.auth.loginFlow.tokenStorageDb }}
Users authenticate via Nextcloud's native Login Flow v2 — no OAuth patches required.
Each user gets a per-device app password managed by the MCP server.
IMPORTANT: Login Flow v2 is experimental. See ADR-022 for details.
{{- end }}
{{- if .Values.documentProcessing.enabled }}
4. Document Processing:
- Enabled: {{ .Values.documentProcessing.enabled }}
- Default processor: {{ .Values.documentProcessing.defaultProcessor }}
{{- if .Values.documentProcessing.unstructured.enabled }}
- Unstructured API: {{ .Values.documentProcessing.unstructured.apiUrl }}
{{- end }}
{{- end }}
{{- if .Values.semanticSearch.enabled }}
5. Semantic Search & Vector Capabilities:
- Semantic Search: Enabled
- Scan Interval: {{ .Values.semanticSearch.scanInterval }}s
- Processor Workers: {{ .Values.semanticSearch.processorWorkers }}
{{- if .Values.qdrant.enabled }}
- Qdrant: Deployed as subchart ({{ .Release.Name }}-qdrant:6333)
{{- else }}
- Qdrant: Not deployed (configure external instance)
{{- end }}
{{- if .Values.ollama.enabled }}
- Ollama: Deployed as subchart ({{ .Release.Name }}-ollama:11434)
- Embedding Model: {{ .Values.ollama.embeddingModel }}
{{- else if .Values.ollama.url }}
- Ollama: Using external instance at {{ .Values.ollama.url }}
- Embedding Model: {{ .Values.ollama.embeddingModel }}
{{- else if .Values.openai.enabled }}
- OpenAI: Enabled for embeddings
{{- else }}
- WARNING: No embedding provider configured (Ollama or OpenAI required)
{{- end }}
Check vector sync status:
kubectl --namespace {{ .Release.Namespace }} exec -it deploy/{{ include "nextcloud-mcp-server.fullname" . }} -- curl -s http://localhost:{{ include "nextcloud-mcp-server.port" . }}/user/page | grep "Vector Sync"
{{- end }}
{{- if .Values.dashboards.enabled }}
6. Grafana Dashboards:
- Dashboard provisioning: Enabled
- ConfigMap: {{ include "nextcloud-mcp-server.fullname" . }}-dashboard
- Grafana Folder: {{ .Values.dashboards.grafanaFolder }}
The dashboard will be automatically imported by Grafana if the sidecar is configured
to watch for ConfigMaps with label "grafana_dashboard: 1".
To manually import the dashboard:
kubectl --namespace {{ .Release.Namespace }} get configmap {{ include "nextcloud-mcp-server.fullname" . }}-dashboard -o jsonpath='{.data.nextcloud-mcp-server\.json}' | jq . > dashboard.json
Then import dashboard.json via Grafana UI (Dashboards → Import).
{{- else }}
6. Grafana Dashboards:
- Dashboard provisioning: Disabled
- To enable automatic dashboard provisioning, set: dashboards.enabled=true
Manual import option:
The dashboard JSON is available in the chart at charts/nextcloud-mcp-server/dashboards/nextcloud-mcp-server.json
{{- end }}
{{- $legacyMultiUserBasic := eq (include "nextcloud-mcp-server.legacyMultiUserBasicPersistence" .) "true" }}
{{- $legacyQdrant := eq (include "nextcloud-mcp-server.legacyQdrantPersistence" .) "true" }}
{{- if or $legacyMultiUserBasic $legacyQdrant }}
================================================================================
DEPRECATION WARNING
================================================================================
You are using deprecated persistence configuration that will be removed in a
future release. Your deployment will continue to work, but please migrate to
the new unified dataStorage configuration.
Deprecated settings detected:
{{- if $legacyMultiUserBasic }}
- auth.multiUserBasic.persistence.* (currently enabled)
{{- end }}
{{- if $legacyQdrant }}
- qdrant.localPersistence.* (currently enabled)
{{- end }}
To migrate, update your values.yaml:
dataStorage:
enabled: true
{{- if $legacyMultiUserBasic }}
size: {{ .Values.auth.multiUserBasic.persistence.size }}
{{- else if $legacyQdrant }}
size: {{ .Values.qdrant.localPersistence.size }}
{{- end }}
# storageClass: "" # Optional: specify storage class
# existingClaim: "" # Optional: use existing PVC to preserve data
After migrating, remove the deprecated settings:
{{- if $legacyMultiUserBasic }}
- auth.multiUserBasic.persistence.enabled
- auth.multiUserBasic.persistence.size
- auth.multiUserBasic.persistence.storageClass
- auth.multiUserBasic.persistence.accessMode
{{- end }}
{{- if $legacyQdrant }}
- qdrant.localPersistence.enabled
- qdrant.localPersistence.size
- qdrant.localPersistence.storageClass
- qdrant.localPersistence.accessMode
{{- end }}
================================================================================
{{- end }}
Deployed version:
- Chart: {{ .Chart.Version }}
- App: {{ .Chart.AppVersion }}
Full changelog: https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/charts/nextcloud-mcp-server/CHANGELOG.md
For more information and documentation:
- GitHub: https://github.com/cbcoutinho/nextcloud-mcp-server
- Documentation: https://github.com/cbcoutinho/nextcloud-mcp-server#readme
To upgrade this deployment:
helm upgrade {{ .Release.Name }} nextcloud-mcp-server
To uninstall:
helm uninstall {{ .Release.Name }}
@@ -1,237 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "nextcloud-mcp-server.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "nextcloud-mcp-server.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "nextcloud-mcp-server.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "nextcloud-mcp-server.labels" -}}
helm.sh/chart: {{ include "nextcloud-mcp-server.chart" . }}
{{ include "nextcloud-mcp-server.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "nextcloud-mcp-server.selectorLabels" -}}
app.kubernetes.io/name: {{ include "nextcloud-mcp-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "nextcloud-mcp-server.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "nextcloud-mcp-server.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use for basic auth
*/}}
{{- define "nextcloud-mcp-server.basicAuthSecretName" -}}
{{- if .Values.auth.basic.existingSecret }}
{{- .Values.auth.basic.existingSecret }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-basic-auth
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use for multi-user basic auth
*/}}
{{- define "nextcloud-mcp-server.multiUserBasicSecretName" -}}
{{- if .Values.auth.multiUserBasic.existingSecret }}
{{- .Values.auth.multiUserBasic.existingSecret }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-multi-user-basic
{{- end }}
{{- end }}
{{/*
Create the name of the PVC to use for multi-user basic token storage
*/}}
{{- define "nextcloud-mcp-server.multiUserBasicPvcName" -}}
{{- if .Values.auth.multiUserBasic.persistence.existingClaim }}
{{- .Values.auth.multiUserBasic.persistence.existingClaim }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-token-storage
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use for OAuth
*/}}
{{- define "nextcloud-mcp-server.oauthSecretName" -}}
{{- if .Values.auth.oauth.existingSecret }}
{{- .Values.auth.oauth.existingSecret }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-oauth
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use for Login Flow v2
*/}}
{{- define "nextcloud-mcp-server.loginFlowSecretName" -}}
{{- if .Values.auth.loginFlow.existingSecret }}
{{- .Values.auth.loginFlow.existingSecret }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-login-flow
{{- end }}
{{- end }}
{{/*
Create the name of the PVC to use for OAuth storage
*/}}
{{- define "nextcloud-mcp-server.oauthPvcName" -}}
{{- if .Values.auth.oauth.persistence.existingClaim }}
{{- .Values.auth.oauth.persistence.existingClaim }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-oauth-storage
{{- end }}
{{- end }}
{{/*
Create the name of the PVC to use for Qdrant local persistent storage
*/}}
{{- define "nextcloud-mcp-server.qdrantPvcName" -}}
{{- if .Values.qdrant.localPersistence.existingClaim }}
{{- .Values.qdrant.localPersistence.existingClaim }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-qdrant-data
{{- end }}
{{- end }}
{{/*
Create the name of the PVC to use for /app/data storage
*/}}
{{- define "nextcloud-mcp-server.dataStoragePvcName" -}}
{{- if .Values.dataStorage.existingClaim }}
{{- .Values.dataStorage.existingClaim }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-data-storage
{{- end }}
{{- end }}
{{/*
Determine if data storage PVC should be enabled (backward compatible)
Checks new dataStorage.enabled OR legacy persistence configs
*/}}
{{- define "nextcloud-mcp-server.dataStorageEnabled" -}}
{{- if .Values.dataStorage.enabled -}}
true
{{- else if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled -}}
true
{{- else if eq .Values.auth.mode "login-flow" -}}
true
{{- else if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled -}}
true
{{- else -}}
false
{{- end -}}
{{- end }}
{{/*
Check if legacy multi-user-basic persistence config is being used
*/}}
{{- define "nextcloud-mcp-server.legacyMultiUserBasicPersistence" -}}
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled (not .Values.dataStorage.enabled) -}}
true
{{- else -}}
false
{{- end -}}
{{- end }}
{{/*
Check if legacy qdrant persistence config is being used
*/}}
{{- define "nextcloud-mcp-server.legacyQdrantPersistence" -}}
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled (not .Values.dataStorage.enabled) -}}
true
{{- else -}}
false
{{- end -}}
{{- end }}
{{/*
Return the MCP server port
*/}}
{{- define "nextcloud-mcp-server.port" -}}
{{- .Values.mcp.port }}
{{- end }}
{{/*
Return the image tag (always uses chart appVersion)
*/}}
{{- define "nextcloud-mcp-server.imageTag" -}}
{{- .Chart.AppVersion }}
{{- end }}
{{/*
Return the public issuer URL for OAuth
Defaults to nextcloud.host if not specified
*/}}
{{- define "nextcloud-mcp-server.publicIssuerUrl" -}}
{{- if .Values.nextcloud.publicIssuerUrl }}
{{- .Values.nextcloud.publicIssuerUrl }}
{{- else }}
{{- .Values.nextcloud.host }}
{{- end }}
{{- end }}
{{/*
Return the MCP server URL for OAuth callbacks
If not specified:
- Uses ingress host if ingress is enabled
- Otherwise defaults to http://localhost:8000 (for port-forward setups)
*/}}
{{- define "nextcloud-mcp-server.mcpServerUrl" -}}
{{- if .Values.nextcloud.mcpServerUrl }}
{{- .Values.nextcloud.mcpServerUrl }}
{{- else if .Values.ingress.enabled }}
{{- $host := index .Values.ingress.hosts 0 }}
{{- if .Values.ingress.tls }}
{{- printf "https://%s" $host.host }}
{{- else }}
{{- printf "http://%s" $host.host }}
{{- end }}
{{- else }}
{{- printf "http://localhost:%d" (int .Values.mcp.port) }}
{{- end }}
{{- end }}
@@ -1,25 +0,0 @@
{{- if .Values.dashboards.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-dashboard
namespace: {{ .Release.Namespace }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
{{- with .Values.dashboards.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
# Grafana sidecar discovery label
grafana_dashboard: "1"
annotations:
{{- with .Values.dashboards.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
# Grafana folder name (annotations support spaces, unlike labels)
{{- if .Values.dashboards.grafanaFolder }}
grafana_folder: {{ .Values.dashboards.grafanaFolder | quote }}
{{- end }}
data:
nextcloud-mcp-server.json: |-
{{ .Files.Get "dashboards/nextcloud-mcp-server.json" | indent 4 }}
{{- end }}
@@ -1,340 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
strategy:
type: Recreate
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "nextcloud-mcp-server.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "nextcloud-mcp-server.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- with .Values.initContainers }}
initContainers:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ include "nextcloud-mcp-server.imageTag" . }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- "--transport"
- "{{ .Values.mcp.transport }}"
{{- if or (eq .Values.auth.mode "oauth") (eq .Values.auth.mode "login-flow") }}
- "--oauth"
{{- end }}
{{- if eq .Values.auth.mode "oauth" }}
- "--oauth-token-type"
- "{{ .Values.auth.oauth.tokenType }}"
{{- end }}
{{- with .Values.mcp.extraArgs }}
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ include "nextcloud-mcp-server.port" . }}
protocol: TCP
{{- if .Values.observability.metrics.enabled }}
- name: metrics
containerPort: {{ .Values.observability.metrics.port }}
protocol: TCP
{{- end }}
env:
# Nextcloud connection
- name: NEXTCLOUD_HOST
value: {{ .Values.nextcloud.host | quote }}
{{- if eq .Values.auth.mode "basic" }}
# Basic auth mode (single-user)
- name: NEXTCLOUD_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.basicAuthSecretName" . }}
key: {{ .Values.auth.basic.usernameKey }}
- name: NEXTCLOUD_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.basicAuthSecretName" . }}
key: {{ .Values.auth.basic.passwordKey }}
{{- else if eq .Values.auth.mode "multi-user-basic" }}
# Multi-user BasicAuth mode (pass-through)
- name: ENABLE_MULTI_USER_BASIC_AUTH
value: "true"
- name: NEXTCLOUD_MCP_SERVER_URL
value: {{ include "nextcloud-mcp-server.mcpServerUrl" . | quote }}
- name: NEXTCLOUD_PUBLIC_ISSUER_URL
value: {{ include "nextcloud-mcp-server.publicIssuerUrl" . | quote }}
{{- if .Values.auth.multiUserBasic.enableOfflineAccess }}
# Background operations with app passwords (replaces deprecated ENABLE_OFFLINE_ACCESS)
- name: ENABLE_BACKGROUND_OPERATIONS
value: "true"
- name: TOKEN_STORAGE_DB
value: {{ .Values.auth.multiUserBasic.tokenStorageDb | quote }}
- name: TOKEN_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }}
key: {{ .Values.auth.multiUserBasic.tokenEncryptionKeyKey }}
- name: NEXTCLOUD_OIDC_SCOPES
value: {{ .Values.auth.multiUserBasic.scopes | quote }}
{{- if or .Values.auth.multiUserBasic.clientId .Values.auth.multiUserBasic.existingSecret }}
# Static OAuth credentials (optional - uses DCR if not provided)
- name: NEXTCLOUD_OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }}
key: {{ .Values.auth.multiUserBasic.clientIdKey }}
- name: NEXTCLOUD_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }}
key: {{ .Values.auth.multiUserBasic.clientSecretKey }}
{{- end }}
{{- end }}
{{- else if eq .Values.auth.mode "oauth" }}
# OAuth mode
- name: NEXTCLOUD_MCP_SERVER_URL
value: {{ include "nextcloud-mcp-server.mcpServerUrl" . | quote }}
- name: NEXTCLOUD_PUBLIC_ISSUER_URL
value: {{ include "nextcloud-mcp-server.publicIssuerUrl" . | quote }}
- name: NEXTCLOUD_OIDC_SCOPES
value: {{ .Values.auth.oauth.scopes | quote }}
{{- if or .Values.auth.oauth.clientId .Values.auth.oauth.existingSecret }}
- name: NEXTCLOUD_OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.oauthSecretName" . }}
key: {{ .Values.auth.oauth.clientIdKey }}
- name: NEXTCLOUD_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.oauthSecretName" . }}
key: {{ .Values.auth.oauth.clientSecretKey }}
{{- end }}
{{- else if eq .Values.auth.mode "login-flow" }}
# Login Flow v2 mode (ADR-022)
- name: ENABLE_LOGIN_FLOW
value: "true"
- name: NEXTCLOUD_MCP_SERVER_URL
value: {{ include "nextcloud-mcp-server.mcpServerUrl" . | quote }}
- name: NEXTCLOUD_PUBLIC_ISSUER_URL
value: {{ include "nextcloud-mcp-server.publicIssuerUrl" . | quote }}
- name: TOKEN_STORAGE_DB
value: {{ .Values.auth.loginFlow.tokenStorageDb | quote }}
- name: TOKEN_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.loginFlowSecretName" . }}
key: {{ .Values.auth.loginFlow.tokenEncryptionKeyKey }}
{{- end }}
{{- if .Values.documentProcessing.enabled }}
# Document processing
- name: ENABLE_DOCUMENT_PROCESSING
value: {{ .Values.documentProcessing.enabled | quote }}
- name: DOCUMENT_PROCESSOR
value: {{ .Values.documentProcessing.defaultProcessor | quote }}
- name: PROGRESS_INTERVAL
value: {{ .Values.documentProcessing.progressInterval | quote }}
{{- if .Values.documentProcessing.unstructured.enabled }}
- name: ENABLE_UNSTRUCTURED
value: "true"
- name: UNSTRUCTURED_API_URL
value: {{ .Values.documentProcessing.unstructured.apiUrl | quote }}
- name: UNSTRUCTURED_TIMEOUT
value: {{ .Values.documentProcessing.unstructured.timeout | quote }}
- name: UNSTRUCTURED_STRATEGY
value: {{ .Values.documentProcessing.unstructured.strategy | quote }}
- name: UNSTRUCTURED_LANGUAGES
value: {{ .Values.documentProcessing.unstructured.languages | quote }}
{{- end }}
{{- if .Values.documentProcessing.tesseract.enabled }}
- name: ENABLE_TESSERACT
value: "true"
{{- if .Values.documentProcessing.tesseract.cmd }}
- name: TESSERACT_CMD
value: {{ .Values.documentProcessing.tesseract.cmd | quote }}
{{- end }}
- name: TESSERACT_LANG
value: {{ .Values.documentProcessing.tesseract.lang | quote }}
{{- end }}
{{- if .Values.documentProcessing.custom.enabled }}
- name: ENABLE_CUSTOM_PROCESSOR
value: "true"
- name: CUSTOM_PROCESSOR_NAME
value: {{ .Values.documentProcessing.custom.name | quote }}
- name: CUSTOM_PROCESSOR_URL
value: {{ .Values.documentProcessing.custom.url | quote }}
{{- if .Values.documentProcessing.custom.apiKey }}
- name: CUSTOM_PROCESSOR_API_KEY
value: {{ .Values.documentProcessing.custom.apiKey | quote }}
{{- end }}
- name: CUSTOM_PROCESSOR_TIMEOUT
value: {{ .Values.documentProcessing.custom.timeout | quote }}
- name: CUSTOM_PROCESSOR_TYPES
value: {{ .Values.documentProcessing.custom.types | quote }}
{{- end }}
{{- end }}
# Semantic Search (replaces deprecated VECTOR_SYNC_ENABLED)
- name: ENABLE_SEMANTIC_SEARCH
value: {{ .Values.semanticSearch.enabled | quote }}
{{- if .Values.semanticSearch.enabled }}
- name: VECTOR_SYNC_SCAN_INTERVAL
value: {{ .Values.semanticSearch.scanInterval | quote }}
- name: VECTOR_SYNC_PROCESSOR_WORKERS
value: {{ .Values.semanticSearch.processorWorkers | quote }}
- name: VECTOR_SYNC_QUEUE_MAX_SIZE
value: {{ .Values.semanticSearch.queueMaxSize | quote }}
{{- end }}
# Document Chunking (always set, used by vector sync processor)
- name: DOCUMENT_CHUNK_SIZE
value: {{ .Values.documentChunking.chunkSize | quote }}
- name: DOCUMENT_CHUNK_OVERLAP
value: {{ .Values.documentChunking.chunkOverlap | quote }}
# Qdrant Vector Database
{{- if eq .Values.qdrant.mode "network" }}
# Network mode: Use dedicated Qdrant service
{{- if .Values.qdrant.networkMode.deploySubchart }}
- name: QDRANT_URL
value: "http://{{ .Release.Name }}-qdrant:6333"
{{- else if .Values.qdrant.networkMode.externalUrl }}
- name: QDRANT_URL
value: {{ .Values.qdrant.networkMode.externalUrl | quote }}
{{- end }}
{{- if or .Values.qdrant.networkMode.apiKey .Values.qdrant.networkMode.existingSecret }}
- name: QDRANT_API_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.qdrant.networkMode.existingSecret | default (printf "%s-qdrant" .Release.Name) }}
key: {{ .Values.qdrant.networkMode.secretKey }}
{{- end }}
{{- else if eq .Values.qdrant.mode "persistent" }}
# Persistent local mode: File-based storage
- name: QDRANT_LOCATION
value: {{ .Values.qdrant.localPersistence.dataPath | quote }}
{{- else }}
# In-memory mode (default): Ephemeral storage
- name: QDRANT_LOCATION
value: ":memory:"
{{- end }}
- name: QDRANT_COLLECTION
value: {{ .Values.qdrant.collection | quote }}
# Ollama Embedding Service
{{- if or .Values.ollama.enabled .Values.ollama.url }}
- name: OLLAMA_BASE_URL
value: {{ .Values.ollama.url | default (printf "http://%s-ollama:11434" .Release.Name) | quote }}
- name: OLLAMA_EMBEDDING_MODEL
value: {{ .Values.ollama.embeddingModel | quote }}
- name: OLLAMA_VERIFY_SSL
value: {{ .Values.ollama.verifySsl | quote }}
{{- end }}
# OpenAI Embedding Provider (alternative to Ollama)
{{- if .Values.openai.enabled }}
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.openai.existingSecret | default (printf "%s-openai" (include "nextcloud-mcp-server.fullname" .)) }}
key: {{ .Values.openai.secretKey }}
{{- if .Values.openai.baseUrl }}
- name: OPENAI_BASE_URL
value: {{ .Values.openai.baseUrl | quote }}
{{- end }}
{{- end }}
# Observability
- name: METRICS_ENABLED
value: {{ .Values.observability.metrics.enabled | quote }}
- name: METRICS_PORT
value: {{ .Values.observability.metrics.port | quote }}
{{- if .Values.observability.tracing.enabled }}
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: {{ .Values.observability.tracing.endpoint | quote }}
- name: OTEL_SERVICE_NAME
value: {{ .Values.observability.tracing.serviceName | quote }}
- name: OTEL_TRACES_SAMPLER_ARG
value: {{ .Values.observability.tracing.samplingRate | quote }}
{{- end }}
- name: LOG_FORMAT
value: {{ .Values.observability.logging.format | quote }}
- name: LOG_LEVEL
value: {{ .Values.observability.logging.level | quote }}
- name: LOG_INCLUDE_TRACE_CONTEXT
value: {{ .Values.observability.logging.includeTraceContext | quote }}
{{- with .Values.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.extraEnvFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
{{- if or (and (eq .Values.auth.mode "oauth") .Values.auth.oauth.persistence.enabled) (eq .Values.auth.mode "login-flow") }}
- name: oauth-storage
mountPath: /app/.oauth
{{- end }}
- name: data-storage
mountPath: /app/data
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: tmp
emptyDir: {}
{{- if or (and (eq .Values.auth.mode "oauth") .Values.auth.oauth.persistence.enabled) (eq .Values.auth.mode "login-flow") }}
- name: oauth-storage
persistentVolumeClaim:
claimName: {{ include "nextcloud-mcp-server.oauthPvcName" . }}
{{- end }}
- name: data-storage
{{- if eq (include "nextcloud-mcp-server.dataStorageEnabled" .) "true" }}
persistentVolumeClaim:
claimName: {{ include "nextcloud-mcp-server.dataStoragePvcName" . }}
{{- else }}
emptyDir: {}
{{- end }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -1,32 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "nextcloud-mcp-server.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
@@ -1,61 +0,0 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "nextcloud-mcp-server.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -1,11 +0,0 @@
{{- if and .Values.openai.enabled (not .Values.openai.existingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-openai
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.openai.secretKey }}: {{ .Values.openai.apiKey | b64enc | quote }}
{{- end }}
@@ -1,92 +0,0 @@
{{- if and .Values.observability.metrics.enabled .Values.prometheusRule.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
{{- with .Values.prometheusRule.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
groups:
- name: nextcloud-mcp-server.critical
interval: 30s
rules:
- alert: NextcloudMCPServerDown
expr: up{job="{{ include "nextcloud-mcp-server.fullname" . }}"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Nextcloud MCP Server is down"
description: "{{ `{{` }} $labels.pod {{ `}}` }} has been down for more than 5 minutes."
- alert: NextcloudMCPHighErrorRate
expr: |
sum(rate(mcp_http_requests_total{status_code=~"5..", job="{{ include "nextcloud-mcp-server.fullname" . }}"}[5m]))
/ sum(rate(mcp_http_requests_total{job="{{ include "nextcloud-mcp-server.fullname" . }}"}[5m])) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate on Nextcloud MCP Server"
description: "Error rate is {{ `{{` }} printf \"%.2f%%\" (mul $value 100) {{ `}}` }} (threshold: 5%)"
- alert: NextcloudMCPHighLatency
expr: |
histogram_quantile(0.95,
sum(rate(mcp_http_request_duration_seconds_bucket{job="{{ include "nextcloud-mcp-server.fullname" . }}"}[5m])) by (le, endpoint)
) > 1
for: 5m
labels:
severity: critical
annotations:
summary: "High latency on Nextcloud MCP Server"
description: "P95 latency is {{ `{{` }} printf \"%.2fs\" $value {{ `}}` }} on {{ `{{` }} $labels.endpoint {{ `}}` }} (threshold: 1s)"
- alert: NextcloudMCPDependencyDown
expr: mcp_dependency_health{job="{{ include "nextcloud-mcp-server.fullname" . }}"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Nextcloud MCP dependency is down"
description: "Dependency {{ `{{` }} $labels.dependency {{ `}}` }} has been down for more than 2 minutes."
- name: nextcloud-mcp-server.warning
interval: 30s
rules:
- alert: NextcloudMCPTokenValidationErrors
expr: |
sum(rate(mcp_oauth_token_validations_total{result="error", job="{{ include "nextcloud-mcp-server.fullname" . }}"}[10m]))
/ sum(rate(mcp_oauth_token_validations_total{job="{{ include "nextcloud-mcp-server.fullname" . }}"}[10m])) > 0.01
for: 10m
labels:
severity: warning
annotations:
summary: "High token validation error rate"
description: "Token validation error rate is {{ `{{` }} printf \"%.2f%%\" (mul $value 100) {{ `}}` }} (threshold: 1%)"
- alert: NextcloudMCPVectorSyncQueueHigh
expr: mcp_vector_sync_queue_size{job="{{ include "nextcloud-mcp-server.fullname" . }}"} > 100
for: 15m
labels:
severity: warning
annotations:
summary: "Vector sync queue is high"
description: "Vector sync queue size is {{ `{{` }} $value {{ `}}` }} (threshold: 100)"
- alert: NextcloudMCPQdrantSlowQueries
expr: |
histogram_quantile(0.95,
sum(rate(mcp_db_operation_duration_seconds_bucket{db="qdrant", job="{{ include "nextcloud-mcp-server.fullname" . }}"}[10m])) by (le)
) > 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "Qdrant queries are slow"
description: "P95 Qdrant query latency is {{ `{{` }} printf \"%.2fs\" $value {{ `}}` }} (threshold: 0.5s)"
{{- end }}
@@ -1,64 +0,0 @@
{{- if and (eq .Values.auth.mode "oauth") .Values.auth.oauth.persistence.enabled (not .Values.auth.oauth.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-oauth-storage
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.auth.oauth.persistence.accessMode }}
{{- if .Values.auth.oauth.persistence.storageClass }}
storageClassName: {{ .Values.auth.oauth.persistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.auth.oauth.persistence.size }}
{{- end }}
---
{{- if eq .Values.auth.mode "login-flow" }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-oauth-storage
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
{{- end }}
---
{{- if and (eq (include "nextcloud-mcp-server.dataStorageEnabled" .) "true") (not .Values.dataStorage.existingClaim) }}
{{- $legacyMultiUserBasic := eq (include "nextcloud-mcp-server.legacyMultiUserBasicPersistence" .) "true" }}
{{- $legacyQdrant := eq (include "nextcloud-mcp-server.legacyQdrantPersistence" .) "true" }}
{{- $accessMode := .Values.dataStorage.accessMode }}
{{- $storageClass := .Values.dataStorage.storageClass }}
{{- $size := .Values.dataStorage.size }}
{{- if $legacyMultiUserBasic }}
{{- $accessMode = .Values.auth.multiUserBasic.persistence.accessMode }}
{{- $storageClass = .Values.auth.multiUserBasic.persistence.storageClass }}
{{- $size = .Values.auth.multiUserBasic.persistence.size }}
{{- else if $legacyQdrant }}
{{- $accessMode = .Values.qdrant.localPersistence.accessMode }}
{{- $storageClass = .Values.qdrant.localPersistence.storageClass }}
{{- $size = .Values.qdrant.localPersistence.size }}
{{- end }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-data-storage
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
accessModes:
- {{ $accessMode }}
{{- if $storageClass }}
storageClassName: {{ $storageClass }}
{{- end }}
resources:
requests:
storage: {{ $size }}
{{- end }}
@@ -1,61 +0,0 @@
{{- if eq .Values.auth.mode "basic" }}
{{- if not .Values.auth.basic.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-basic-auth
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.auth.basic.usernameKey }}: {{ .Values.auth.basic.username | b64enc | quote }}
{{ .Values.auth.basic.passwordKey }}: {{ .Values.auth.basic.password | b64enc | quote }}
{{- end }}
{{- end }}
---
{{- if eq .Values.auth.mode "multi-user-basic" }}
{{- if and .Values.auth.multiUserBasic.enableOfflineAccess (not .Values.auth.multiUserBasic.existingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-multi-user-basic
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.auth.multiUserBasic.tokenEncryptionKeyKey }}: {{ .Values.auth.multiUserBasic.tokenEncryptionKey | b64enc | quote }}
{{- if .Values.auth.multiUserBasic.clientId }}
{{ .Values.auth.multiUserBasic.clientIdKey }}: {{ .Values.auth.multiUserBasic.clientId | b64enc | quote }}
{{ .Values.auth.multiUserBasic.clientSecretKey }}: {{ .Values.auth.multiUserBasic.clientSecret | b64enc | quote }}
{{- end }}
{{- end }}
{{- end }}
---
{{- if eq .Values.auth.mode "oauth" }}
{{- if and .Values.auth.oauth.clientId (not .Values.auth.oauth.existingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-oauth
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.auth.oauth.clientIdKey }}: {{ .Values.auth.oauth.clientId | b64enc | quote }}
{{ .Values.auth.oauth.clientSecretKey }}: {{ .Values.auth.oauth.clientSecret | b64enc | quote }}
{{- end }}
{{- end }}
---
{{- if eq .Values.auth.mode "login-flow" }}
{{- if not .Values.auth.loginFlow.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-login-flow
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.auth.loginFlow.tokenEncryptionKeyKey }}: {{ .Values.auth.loginFlow.tokenEncryptionKey | b64enc | quote }}
{{- end }}
{{- end }}
@@ -1,25 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
{{- if .Values.observability.metrics.enabled }}
- port: {{ .Values.observability.metrics.port }}
targetPort: metrics
protocol: TCP
name: metrics
{{- end }}
selector:
{{- include "nextcloud-mcp-server.selectorLabels" . | nindent 4 }}
@@ -1,13 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "nextcloud-mcp-server.serviceAccountName" . }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}
@@ -1,32 +0,0 @@
{{- if and .Values.observability.metrics.enabled .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
{{- with .Values.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "nextcloud-mcp-server.selectorLabels" . | nindent 6 }}
endpoints:
- port: metrics
path: {{ .Values.observability.metrics.path }}
interval: {{ .Values.serviceMonitor.interval }}
scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }}
scheme: http
relabelings:
# Add namespace label
- sourceLabels: [__meta_kubernetes_namespace]
targetLabel: namespace
# Add pod label
- sourceLabels: [__meta_kubernetes_pod_name]
targetLabel: pod
# Add service label
- sourceLabels: [__meta_kubernetes_service_name]
targetLabel: service
{{- end }}
-553
View File
@@ -1,553 +0,0 @@
# Default values for nextcloud-mcp-server
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# Number of replicas
replicaCount: 1
image:
repository: ghcr.io/cbcoutinho/nextcloud-mcp-server
pullPolicy: IfNotPresent
# Image tag is automatically set to chart appVersion
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# Nextcloud connection settings
nextcloud:
# URL of your Nextcloud instance (required)
# Example: https://cloud.example.com
host: ""
# MCP server URL for OAuth callbacks (OAuth mode only)
# If not specified, will be constructed from ingress.hosts[0] if ingress is enabled,
# or defaults to http://localhost:8000 (suitable for port-forward setups)
# Example: https://mcp.example.com
mcpServerUrl: ""
# Public issuer URL for browser-accessible OAuth authorization endpoints (OAuth mode only)
# ONLY used to make authorization endpoints accessible to users' browsers
# All server-to-server communication (token endpoint, JWKS, introspection, userinfo)
# uses URLs from OIDC discovery without any rewriting
#
# Use case: When MCP server accesses Nextcloud at one URL but browsers need a different
# public URL for OAuth login (e.g., server uses internal DNS, browsers use public domain)
#
# If not specified, defaults to nextcloud.host (works when MCP server and browsers
# both access Nextcloud at the same URL)
# Example: https://cloud.example.com
publicIssuerUrl: ""
# Authentication configuration
# Choose one mode: "basic", "multi-user-basic", "oauth", or "login-flow"
auth:
# Authentication mode: "basic", "multi-user-basic", "oauth", or "login-flow"
# basic: Single-user with username/password (recommended for personal use)
# multi-user-basic: Multi-user with BasicAuth pass-through (credentials in request headers)
# oauth: Uses OAuth2/OIDC (experimental, requires patches)
# login-flow: Multi-user via Nextcloud Login Flow v2 (experimental, ADR-022)
mode: basic
# Basic authentication settings (single-user mode)
basic:
# Nextcloud username (ignored if existingSecret is set)
username: ""
# Nextcloud password or app password (recommended) (ignored if existingSecret is set)
password: ""
# Use existing secret instead of creating one
# If set, username and password above are ignored
# Secret must contain keys specified in usernameKey and passwordKey
# Example:
# kubectl create secret generic my-nextcloud-creds \
# --from-literal=username=myuser \
# --from-literal=password=mypassword
existingSecret: ""
# Keys in the existing secret
usernameKey: "username"
passwordKey: "password"
# Multi-user BasicAuth settings (pass-through mode)
# Users provide credentials in request headers (Authorization: Basic ...)
# Server optionally stores app passwords for background operations
multiUserBasic:
# Enable offline access (background operations using app passwords via Astrolabe)
# When enabled, requires token encryption key. OAuth client credentials are optional (uses DCR if not provided)
enableOfflineAccess: false
# Token encryption key (required if enableOfflineAccess: true, ignored if existingSecret is set)
# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
tokenEncryptionKey: ""
# Token storage database path
tokenStorageDb: "/app/data/tokens.db"
# OAuth client credentials (optional - uses Dynamic Client Registration if not provided)
# Only needed if enableOfflineAccess: true
clientId: ""
clientSecret: ""
# OAuth scopes to request (space-separated)
scopes: "openid profile email offline_access notes.read notes.write calendar.read calendar.write contacts.read contacts.write cookbook.read cookbook.write deck.read deck.write tables.read tables.write files.read files.write sharing.read sharing.write todo.read todo.write"
# Use existing secret for multi-user basic auth credentials
# If set, tokenEncryptionKey, clientId, and clientSecret above are ignored
# Secret should contain keys specified in the *Key fields below
# Example:
# kubectl create secret generic my-multiuser-creds \
# --from-literal=token_encryption_key=ESF1BvEQ... \
# --from-literal=client_id=my-client-id \
# --from-literal=client_secret=my-client-secret
existingSecret: ""
# Keys in the existing secret
tokenEncryptionKeyKey: "token_encryption_key"
clientIdKey: "client_id"
clientSecretKey: "client_secret"
# Persistent storage for token database
persistence:
enabled: true
# Storage class (leave empty for default)
storageClass: ""
accessMode: ReadWriteOnce
size: 100Mi
# Use existing PVC
existingClaim: ""
# OAuth2/OIDC settings (experimental)
oauth:
# OAuth token type: "jwt" or "opaque"
tokenType: "jwt"
# Pre-registered OAuth client ID (optional, ignored if existingSecret is set)
# If not provided and no existingSecret, will use Dynamic Client Registration (DCR)
clientId: ""
# Pre-registered OAuth client secret (optional, ignored if existingSecret is set)
clientSecret: ""
# OAuth scopes to request (space-separated)
scopes: "openid profile email notes.read notes.write calendar.read calendar.write contacts.read contacts.write cookbook.read cookbook.write deck.read deck.write tables.read tables.write files.read files.write sharing.read sharing.write todo.read todo.write"
# Use existing secret for OAuth client credentials
# If set, clientId and clientSecret above are ignored
# Secret must contain keys specified in clientIdKey and clientSecretKey
# Example:
# kubectl create secret generic my-oauth-creds \
# --from-literal=clientId=my-client-id \
# --from-literal=clientSecret=my-client-secret
existingSecret: ""
# Keys in the existing secret
clientIdKey: "clientId"
clientSecretKey: "clientSecret"
# Persistent storage for OAuth client credentials
persistence:
enabled: true
# Storage class (leave empty for default)
storageClass: ""
accessMode: ReadWriteOnce
size: 100Mi
# Use existing PVC
existingClaim: ""
# Login Flow v2 settings (experimental, ADR-022)
# Uses Nextcloud's native Login Flow v2 to obtain app passwords per user.
# No OAuth patches required — works with stock Nextcloud.
# See: docs/ADR-022-deployment-mode-consolidation.md
loginFlow:
# Token encryption key (required, ignored if existingSecret is set)
# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
tokenEncryptionKey: ""
# Token storage database path
tokenStorageDb: "/app/data/tokens.db"
# Use existing secret instead of creating one
existingSecret: ""
# Key in the existing secret
tokenEncryptionKeyKey: "token_encryption_key"
# Data Storage Configuration
# Persistent volume for /app/data directory
# Used for: token databases, qdrant persistent storage, and any app data
# When disabled, uses emptyDir (non-persistent, but still writable)
dataStorage:
# Enable persistent storage for /app/data
# Set to true when using:
# - Multi-user basic auth with offline access (stores tokens.db)
# - Login flow mode (stores app passwords in tokens.db)
# - Qdrant persistent mode (stores vector database)
# - Any feature requiring persistent app data
# Set to false for basic auth without persistence (uses emptyDir)
enabled: false
# Storage class (leave empty for default)
storageClass: ""
accessMode: ReadWriteOnce
# Size for data storage (should accommodate tokens.db and/or qdrant data)
# Recommended: 1Gi minimum, 5Gi for production with qdrant
size: 1Gi
# Use existing PVC
existingClaim: ""
# MCP server configuration
mcp:
# Transport mode (default: streamable-http for SSE)
transport: "streamable-http"
# Port for MCP server (both basic auth and OAuth modes)
port: 8000
# Additional command-line arguments to pass to nextcloud-mcp-server
# Example: ["--log-level", "debug", "--enable-app", "notes"]
extraArgs: []
# Document processing configuration (optional)
documentProcessing:
# Enable document processing (PDF, DOCX, images, etc.)
enabled: false
# Default processor: unstructured, tesseract, or custom
defaultProcessor: "unstructured"
# Progress reporting interval in seconds
progressInterval: 10
# Unstructured.io processor
unstructured:
enabled: false
# Unstructured API endpoint
apiUrl: "http://unstructured:8000"
# Request timeout in seconds
timeout: 120
# Parsing strategy: auto, fast, or hi_res
strategy: "auto"
# OCR languages (comma-separated ISO 639-3 codes)
languages: "eng,deu"
# Tesseract processor (local OCR)
tesseract:
enabled: false
# Path to tesseract executable (optional, auto-detected if in PATH)
cmd: ""
# OCR language (e.g., eng, deu, eng+deu for multiple)
lang: "eng"
# Custom processor
custom:
enabled: false
# Unique name for your processor
name: "my_ocr"
# Custom processor API endpoint
url: ""
# Optional API key for authentication
apiKey: ""
# Request timeout in seconds
timeout: 60
# Comma-separated MIME types your processor supports
types: "application/pdf,image/jpeg,image/png"
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podLabels: {}
podSecurityContext:
fsGroup: 2000
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
# Observability Configuration
observability:
# Prometheus metrics
metrics:
enabled: true
port: 9090
path: /metrics
# OpenTelemetry tracing
tracing:
enabled: false
endpoint: "" # e.g., "http://opentelemetry-collector:4317"
serviceName: "nextcloud-mcp-server"
samplingRate: 1.0
# Logging configuration
logging:
format: json # "json" or "text"
level: INFO
includeTraceContext: true
# Prometheus ServiceMonitor (requires Prometheus Operator)
serviceMonitor:
enabled: false
interval: 30s
scrapeTimeout: 10s
labels: {}
# Additional labels for ServiceMonitor (e.g., for Prometheus selector)
# Example: { prometheus: kube-prometheus }
# Prometheus alert rules (requires Prometheus Operator)
prometheusRule:
enabled: false
labels: {}
# Additional labels for PrometheusRule (e.g., for Prometheus selector)
# Example: { prometheus: kube-prometheus }
# Grafana dashboards (requires Grafana with sidecar enabled)
dashboards:
# Enable automatic dashboard provisioning via ConfigMap
enabled: false
# Grafana folder name where dashboards will be imported
# The grafana-sidecar looks for ConfigMaps with label "grafana_dashboard: 1"
# and reads the folder name from annotation "grafana_folder" (supports spaces)
grafanaFolder: "Nextcloud MCP"
# Additional labels for dashboard ConfigMap
# These will be added alongside the required "grafana_dashboard: 1" label
labels: {}
# Additional annotations for dashboard ConfigMap
annotations: {}
service:
type: ClusterIP
port: 8000
annotations: {}
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: mcp.example.com
paths:
- path: /
pathType: Prefix
tls: []
# - secretName: nextcloud-mcp-tls
# hosts:
# - mcp.example.com
resources:
# We recommend setting resource requests and limits
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
# Liveness probe configuration
# Checks if the application process is running
livenessProbe:
httpGet:
path: /health/live
port: http
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Readiness probe configuration
# Checks if the application is ready to serve traffic
readinessProbe:
httpGet:
path: /health/ready
port: http
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# Autoscaling configuration
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
# Init containers
initContainers: []
# Additional environment variables
extraEnv: []
# - name: CUSTOM_VAR
# value: "custom_value"
# Additional environment variables from ConfigMaps or Secrets
extraEnvFrom: []
# - configMapRef:
# name: my-configmap
# - secretRef:
# name: my-secret
# Semantic Search Configuration
# Enable semantic search with BM25 hybrid search and background synchronization
# of Nextcloud content into vector database
semanticSearch:
# Enable semantic search and background vector synchronization
enabled: false
# Scan interval in seconds (how often to check for changes)
scanInterval: 3600
# Number of concurrent processor workers
processorWorkers: 3
# Maximum queue size for documents pending indexing
queueMaxSize: 10000
# Document Chunking Configuration
# Controls how documents are split into chunks before embedding
# Only relevant when semanticSearch.enabled is true
documentChunking:
# Number of words per chunk (default: 512)
# Smaller chunks (256-384): Better for precise searches, more chunks to store
# Medium chunks (512-768): Balanced approach (recommended for most use cases)
# Larger chunks (1024+): Better for context, less precise matching
chunkSize: 512
# Number of overlapping words between chunks (default: 50)
# Recommended: 10-20% of chunkSize for context preservation across boundaries
# Must be less than chunkSize
chunkOverlap: 50
# Qdrant Vector Database Configuration
# Three deployment modes available:
# 1. Local In-Memory: Fast, ephemeral, zero-config (mode: "memory")
# 2. Local Persistent: File-based, survives restarts (mode: "persistent")
# 3. Network: Dedicated Qdrant service, production-ready (mode: "network")
qdrant:
# Qdrant mode: "memory", "persistent", or "network"
# - memory: In-memory storage (:memory:) - default, zero config, data lost on restart
# - persistent: Local file storage - data persists across restarts, suitable for small/medium deployments
# - network: Dedicated Qdrant service (see networkMode below)
mode: "memory"
# Collection name for vector data
collection: "nextcloud_content"
# Local persistent mode configuration (only used when mode: "persistent")
localPersistence:
# Enable persistent volume for local Qdrant data
enabled: true
# Storage class (leave empty for default)
storageClass: ""
accessMode: ReadWriteOnce
# Size for local Qdrant storage
size: 1Gi
# Path where Qdrant data is stored (relative to /app/data)
# Default: /app/data/qdrant
dataPath: "/app/data/qdrant"
# Use existing PVC
existingClaim: ""
# Network mode configuration (only used when mode: "network")
networkMode:
# Deploy Qdrant as a subchart (if true) or use external Qdrant (if false)
deploySubchart: false
# External Qdrant URL (used when deploySubchart: false)
# Example: "http://qdrant.default.svc.cluster.local:6333"
externalUrl: ""
# Optional API key for Qdrant authentication
apiKey: ""
# Use existing secret for API key
existingSecret: ""
secretKey: "api-key"
# Qdrant subchart configuration (only used when mode: "network" and networkMode.deploySubchart: true)
# All values are passed through to the qdrant/qdrant chart.
# See https://github.com/qdrant/qdrant-helm for full configuration options.
subchart:
# Number of Qdrant replicas
replicaCount: 1
image:
# Qdrant version
tag: v1.12.5
config:
cluster:
# Enable distributed cluster mode
enabled: false
# Persistent storage for vector data
persistence:
size: 10Gi
storageClass: ""
accessModes:
- ReadWriteOnce
# Resource limits and requests
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
# Ollama Embedding Service
# Deployed as a subchart when enabled. All values are passed through to the ollama/ollama chart.
# See https://github.com/otwld/ollama-helm for full configuration options.
ollama:
# Enable Ollama subchart deployment
# Set to true to deploy Ollama as a subchart, or false to use an external Ollama instance
enabled: false
# External Ollama URL (use this if you have Ollama deployed elsewhere)
# When set, use enabled: false to prevent deploying the subchart
# Example: "http://ollama.default.svc.cluster.local:11434"
url: ""
# Embedding model to use
embeddingModel: "nomic-embed-text"
# Verify SSL certificates when connecting to Ollama
verifySsl: true
# Number of Ollama replicas (only used when subchart is deployed)
replicaCount: 1
# Ollama configuration (only used when subchart is deployed)
ollama:
# Models to automatically pull on startup
models:
pull:
- nomic-embed-text
# Persistent storage for models (only used when subchart is deployed)
persistentVolume:
enabled: true
size: 20Gi
storageClass: ""
# Resource limits and requests (only used when subchart is deployed)
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
# OpenAI-compatible Embedding Provider
# Alternative to Ollama for embedding generation. Can be used with OpenAI or any compatible API.
openai:
# Enable OpenAI embedding provider
enabled: false
# OpenAI API key (only used if existingSecret is not set)
apiKey: ""
# Name of existing secret containing the API key
existingSecret: ""
# Key in the secret that contains the API key
secretKey: "api-key"
# Optional custom API endpoint (e.g., for Azure OpenAI or local compatible services)
baseUrl: ""
+3 -32
View File
@@ -27,15 +27,7 @@ Access metrics at: `http://localhost:9090/metrics`
### Kubernetes Deployment ### Kubernetes Deployment
Metrics are automatically scraped if you have Prometheus Operator installed: For Kubernetes deployments with Helm, see the [Helm chart repository](https://github.com/cbcoutinho/helm-charts) which includes ServiceMonitor and PrometheusRule support.
```bash
helm install nextcloud-mcp charts/nextcloud-mcp-server \
--set observability.metrics.enabled=true \
--set observability.tracing.enabled=true \
--set observability.tracing.endpoint=http://opentelemetry-collector:4317 \
--set serviceMonitor.enabled=true
```
## Configuration ## Configuration
@@ -55,28 +47,7 @@ helm install nextcloud-mcp charts/nextcloud-mcp-server \
### Helm Chart Configuration ### Helm Chart Configuration
```yaml The Helm chart has moved to a [separate repository](https://github.com/cbcoutinho/helm-charts). See its `values.yaml` for observability configuration options including metrics, tracing, logging, and ServiceMonitor settings.
observability:
metrics:
enabled: true
port: 9090
path: /metrics
tracing:
enabled: true
endpoint: "http://opentelemetry-collector:4317"
samplingRate: 1.0
logging:
format: json
level: INFO
includeTraceContext: true
serviceMonitor:
enabled: true
interval: 30s
scrapeTimeout: 10s
```
## Metrics ## Metrics
@@ -206,7 +177,7 @@ sum(rate(mcp_nextcloud_api_requests_total{status_code!~"2.."}[5m])) by (app)
- Vector sync queue >100 for >15min - Vector sync queue >100 for >15min
- Qdrant slow (p95 >500ms) for >10min - Qdrant slow (p95 >500ms) for >10min
See `charts/nextcloud-mcp-server/templates/prometheusrule.yaml` for complete definitions. See the [Helm chart repository](https://github.com/cbcoutinho/helm-charts) for PrometheusRule definitions.
## Troubleshooting ## Troubleshooting
+1 -1
View File
@@ -385,7 +385,7 @@ services:
### Scaling with Multiple Replicas ### Scaling with Multiple Replicas
For higher load, use Docker Swarm or Kubernetes. See the [Helm Chart](../helm/) for Kubernetes deployments. For higher load, use Docker Swarm or Kubernetes. See the [Helm chart](https://github.com/cbcoutinho/helm-charts) for Kubernetes deployments.
--- ---
-15
View File
@@ -93,21 +93,6 @@ version_provider = "uv"
update_changelog_on_bump = true update_changelog_on_bump = true
major_version_zero = true major_version_zero = true
# MCP server version files + Helm appVersion
version_files = [
"charts/nextcloud-mcp-server/Chart.yaml:^appVersion:",
]
# Ignore tags from other components
ignored_tag_formats = [
"nextcloud-mcp-server-*", # Helm chart tags
]
# Filter commits by scope (all scopes except helm)
[tool.commitizen.customize]
changelog_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)(?!\\((?:helm)\\))(\\([^)]+\\))?(!)?:"
schema_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)(?!\\((?:helm)\\))(\\([^)]+\\))?(!)?:\\s.+"
[tool.ruff.lint] [tool.ruff.lint]
extend-select = ["I", "PLC0415"] extend-select = ["I", "PLC0415"]
-86
View File
@@ -1,86 +0,0 @@
#!/bin/bash
# Bump Helm chart version
set -euo pipefail
# Parse optional --increment flag
INCREMENT=""
while [[ $# -gt 0 ]]; do
case $1 in
--increment)
INCREMENT="$2"
shift 2
;;
*)
echo "❌ Error: Unknown option: $1" >&2
echo "Usage: $0 [--increment PATCH|MINOR|MAJOR]" >&2
exit 1
;;
esac
done
# Validate dependencies
command -v uv >/dev/null 2>&1 || {
echo "❌ Error: uv not found" >&2
echo " Install from https://docs.astral.sh/uv/" >&2
exit 1
}
# Validate Helm chart directory exists
if [ ! -d "charts/nextcloud-mcp-server" ]; then
echo "❌ Error: Must run from repository root (charts/ not found)" >&2
exit 1
fi
cd charts/nextcloud-mcp-server
# Validate Chart.yaml exists
if [ ! -f "Chart.yaml" ]; then
echo "❌ Error: Chart.yaml not found" >&2
exit 1
fi
echo "Bumping Helm chart version..."
if [ -n "$INCREMENT" ]; then
echo " Forcing $INCREMENT bump"
fi
# Build commitizen command
CZ_CMD="uv run cz --config .cz.toml bump --yes"
if [ -n "$INCREMENT" ]; then
CZ_CMD="$CZ_CMD --increment $INCREMENT"
fi
# Run commitizen bump and capture output
if ! output=$($CZ_CMD 2>&1); then
cd ../..
# Check if this is the expected "no commits to bump" case
if echo "$output" | grep -q "\[NO_COMMITS_TO_BUMP\]"; then
echo "️ No commits eligible for version bump" >&2
echo "$output" >&2
exit 0
fi
# Otherwise, this is an actual error
echo "❌ Error: Version bump failed" >&2
echo "$output" >&2
echo "" >&2
echo "Common causes:" >&2
echo " - No commits with scope 'helm' since last version" >&2
echo " - No conventional commits found (use feat(helm):, fix(helm):, etc.)" >&2
echo " - Git working directory not clean" >&2
exit 1
fi
echo "$output"
echo ""
echo "✓ Helm chart version bumped successfully"
echo " Updated: Chart.yaml:version"
echo " Tag format: nextcloud-mcp-server-\${version}"
echo " Note: appVersion stays at MCP server version"
echo ""
echo "Next steps:"
echo " cd ../.."
echo " git push --follow-tags"
cd ../..
+1 -1
View File
@@ -65,7 +65,7 @@ fi
echo "$output" echo "$output"
echo "" echo ""
echo "✓ MCP server version bumped successfully" echo "✓ MCP server version bumped successfully"
echo " Updated: pyproject.toml, Chart.yaml:appVersion" echo " Updated: pyproject.toml"
echo " Tag format: v\${version}" echo " Tag format: v\${version}"
echo "" echo ""
echo "Next steps:" echo "Next steps:"
+3 -13
View File
@@ -6,8 +6,7 @@ echo "Testing commitizen scope filtering patterns..."
echo echo
# Regex patterns from configs # Regex patterns from configs
MCP_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)(?!\((?:helm|astrolabe)\))(\([^)]+\))?(!)?:' MCP_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)(?!\((?:astrolabe)\))(\([^)]+\))?(!)?:'
HELM_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)\(helm\)(!)?:'
ASTROLABE_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)\(astrolabe\)(!)?:' ASTROLABE_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)\(astrolabe\)(!)?:'
test_pattern() { test_pattern() {
@@ -31,9 +30,6 @@ run_test() {
if test_pattern "$message" "$MCP_PATTERN"; then if test_pattern "$message" "$MCP_PATTERN"; then
matched_components+=("mcp") matched_components+=("mcp")
fi fi
if test_pattern "$message" "$HELM_PATTERN"; then
matched_components+=("helm")
fi
if test_pattern "$message" "$ASTROLABE_PATTERN"; then if test_pattern "$message" "$ASTROLABE_PATTERN"; then
matched_components+=("astrolabe") matched_components+=("astrolabe")
fi fi
@@ -62,7 +58,7 @@ run_test() {
failed=0 failed=0
passed=0 passed=0
# MCP server commits (any scope except helm/astrolabe) # MCP server commits (any scope except astrolabe)
run_test "feat: add new feature" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) run_test "feat: add new feature" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "feat(mcp): add API endpoint" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) run_test "feat(mcp): add API endpoint" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "fix(mcp): resolve authentication bug" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) run_test "fix(mcp): resolve authentication bug" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
@@ -71,11 +67,6 @@ run_test "fix(ci): update workflow" "mcp" && passed=$((passed+1)) || failed=$((f
run_test "feat(api): add endpoint" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) run_test "feat(api): add endpoint" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "ci: configure GitHub Actions" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) run_test "ci: configure GitHub Actions" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
# Helm chart commits
run_test "feat(helm): add resource limits" "helm" && passed=$((passed+1)) || failed=$((failed+1))
run_test "fix(helm): correct values schema" "helm" && passed=$((passed+1)) || failed=$((failed+1))
run_test "docs(helm): update deployment guide" "helm" && passed=$((passed+1)) || failed=$((failed+1))
# Astrolabe commits # Astrolabe commits
run_test "feat(astrolabe): add dark mode" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1)) run_test "feat(astrolabe): add dark mode" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1))
run_test "fix(astrolabe): resolve UI bug" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1)) run_test "fix(astrolabe): resolve UI bug" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1))
@@ -83,11 +74,10 @@ run_test "perf(astrolabe): optimize rendering" "astrolabe" && passed=$((passed+1
# Breaking changes # Breaking changes
run_test "feat(mcp)!: breaking API change" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) run_test "feat(mcp)!: breaking API change" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "feat(helm)!: rename values" "helm" && passed=$((passed+1)) || failed=$((failed+1))
run_test "feat(astrolabe)!: remove deprecated feature" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1)) run_test "feat(astrolabe)!: remove deprecated feature" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1))
# Edge cases # Edge cases
run_test "feat(invalid): test" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) # Any scope except helm/astrolabe → MCP run_test "feat(invalid): test" "mcp" && passed=$((passed+1)) || failed=$((failed+1)) # Any scope except astrolabe → MCP
run_test "random commit message" "none" && passed=$((passed+1)) || failed=$((failed+1)) # Not conventional commit run_test "random commit message" "none" && passed=$((passed+1)) || failed=$((failed+1)) # Not conventional commit
run_test "feat (mcp): space before scope" "none" && passed=$((passed+1)) || failed=$((failed+1)) # Invalid format run_test "feat (mcp): space before scope" "none" && passed=$((passed+1)) || failed=$((failed+1)) # Invalid format