From 2ad020761e7bbe9ffdddedcd3b0eb34a0250063a Mon Sep 17 00:00:00 2001 From: sHa Date: Fri, 20 Mar 2026 16:40:23 +0200 Subject: [PATCH] Add Helm chart and Kubernetes deployment configuration for Redmine MCP --- .github/workflows/build.yml | 32 +++++++++++++++- README.md | 70 +++++++++++++++++++++++++++++++++- helm/Chart.yaml | 6 +++ helm/templates/_helpers.tpl | 36 +++++++++++++++++ helm/templates/deployment.yaml | 58 ++++++++++++++++++++++++++++ helm/templates/secret.yaml | 12 ++++++ helm/templates/service.yaml | 15 ++++++++ helm/values.yaml | 29 ++++++++++++++ 8 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 helm/Chart.yaml create mode 100644 helm/templates/_helpers.tpl create mode 100644 helm/templates/deployment.yaml create mode 100644 helm/templates/secret.yaml create mode 100644 helm/templates/service.yaml create mode 100644 helm/values.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67a7e37..4e04812 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,12 +9,18 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: read + contents: write packages: write steps: - name: Checkout uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set image tag + id: tag + run: echo "value=sha-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -36,6 +42,28 @@ jobs: file: container/Containerfile platforms: linux/amd64,linux/arm64 push: true - tags: ghcr.io/${{ github.repository }}:latest + tags: ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.value }} cache-from: type=gha cache-to: type=gha,mode=max + + - name: Bump chart version and set appVersion + id: chart + run: | + CURRENT=$(grep '^version:' helm/Chart.yaml | awk '{print $2}') + MAJOR=$(echo $CURRENT | cut -d. -f1) + MINOR=$(echo $CURRENT | cut -d. -f2) + PATCH=$(echo $CURRENT | cut -d. -f3) + NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" + + sed -i "s/^version: .*/version: $NEW_VERSION/" helm/Chart.yaml + sed -i "s/^appVersion: .*/appVersion: \"${{ steps.tag.outputs.value }}\"/" helm/Chart.yaml + + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Commit and push chart update + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add helm/Chart.yaml + git commit -m "chore: chart ${{ steps.chart.outputs.version }}, appVersion ${{ steps.tag.outputs.value }} [skip ci]" + git push diff --git a/README.md b/README.md index dcec8fa..2361df1 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ Exposes Redmine REST API as tools for Claude Code via MCP (Model Context Protoco ## Available tools -| Tool | Description | -|------|-------------| **Issues** | Tool | Description | @@ -87,6 +85,74 @@ Verify it was added: claude mcp list ``` +## Kubernetes / k3s + +### Quick start + +**1. Install Helm** (if not already installed): +```bash +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +``` + +**2. Install the chart:** +```bash +helm install redmine-mcp ./helm \ + --set redmine.url=https://your.redmine.com \ + --set redmine.apiKey=your_api_key +``` + +**3. Register with Claude Code** (using port-forward): +```bash +kubectl port-forward svc/redmine-mcp 8000:8000 +claude mcp add --scope user --transport http redmine http://localhost:8000/mcp +``` + +--- + +### Expose via NodePort (access without port-forward) + +```bash +helm install redmine-mcp ./helm \ + --set redmine.url=https://your.redmine.com \ + --set redmine.apiKey=your_api_key \ + --set service.type=NodePort +``` + +Then find the assigned port: +```bash +kubectl get svc redmine-mcp +``` + +Register: +```bash +claude mcp add --scope user --transport http redmine http://:/mcp +``` + +--- + +### Use an existing Kubernetes Secret + +```bash +kubectl create secret generic redmine-credentials \ + --from-literal=REDMINE_URL=https://your.redmine.com \ + --from-literal=REDMINE_API_KEY=your_api_key + +helm install redmine-mcp ./helm \ + --set redmine.existingSecret=redmine-credentials +``` + +--- + +### Upgrade / uninstall + +```bash +# Upgrade +helm upgrade redmine-mcp ./helm --reuse-values + +# Uninstall +helm uninstall redmine-mcp +``` + ## Usage examples Once registered, just ask Claude naturally in any project: diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000..acf1de1 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: redmine-mcp +description: Redmine MCP Server — exposes Redmine REST API as MCP tools for Claude Code +type: application +version: 0.1.0 +appVersion: "0.0.0" # updated automatically by CI diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 0000000..e962772 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,36 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "redmine-mcp.name" -}} +{{- .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "redmine-mcp.labels" -}} +helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} +app.kubernetes.io/name: {{ include "redmine-mcp.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "redmine-mcp.selectorLabels" -}} +app.kubernetes.io/name: {{ include "redmine-mcp.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Name of the secret containing Redmine credentials +*/}} +{{- define "redmine-mcp.secretName" -}} +{{- if .Values.redmine.existingSecret -}} +{{ .Values.redmine.existingSecret }} +{{- else -}} +{{ include "redmine-mcp.name" . }}-credentials +{{- end }} +{{- end }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml new file mode 100644 index 0000000..6e2a55b --- /dev/null +++ b/helm/templates/deployment.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "redmine-mcp.name" . }} + labels: + {{- include "redmine-mcp.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "redmine-mcp.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "redmine-mcp.labels" . | nindent 8 }} + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + envFrom: + - secretRef: + name: {{ include "redmine-mcp.secretName" . }} + env: + - name: MCP_TRANSPORT + value: http + - name: MCP_HOST + value: "0.0.0.0" + - name: MCP_PORT + value: "8000" + livenessProbe: + tcpSocket: + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: http + initialDelaySeconds: 3 + periodSeconds: 5 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/templates/secret.yaml b/helm/templates/secret.yaml new file mode 100644 index 0000000..148f01b --- /dev/null +++ b/helm/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- if not .Values.redmine.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "redmine-mcp.name" . }}-credentials + labels: + {{- include "redmine-mcp.labels" . | nindent 4 }} +type: Opaque +stringData: + REDMINE_URL: {{ .Values.redmine.url | required "redmine.url is required" | quote }} + REDMINE_API_KEY: {{ .Values.redmine.apiKey | required "redmine.apiKey is required" | quote }} +{{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 0000000..ac34de5 --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "redmine-mcp.name" . }} + labels: + {{- include "redmine-mcp.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "redmine-mcp.selectorLabels" . | nindent 4 }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: http + protocol: TCP diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000..6db4a7b --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,29 @@ +replicaCount: 1 + +image: + repository: ghcr.io/shadoll/redmine-mcp + tag: "" # defaults to Chart.appVersion when empty + pullPolicy: Always + +redmine: + # Inline credentials (will be stored in a Secret) + url: "" + apiKey: "" + # -- Or reference an existing Secret with keys REDMINE_URL and REDMINE_API_KEY + existingSecret: "" + +service: + type: ClusterIP + port: 8000 + +resources: {} + # limits: + # cpu: 200m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 64Mi + +nodeSelector: {} +tolerations: [] +affinity: {}