diff --git a/.github/workflows/chart-preview.yml b/.github/workflows/chart-preview.yml
index 7b45d60f..3af6d438 100644
--- a/.github/workflows/chart-preview.yml
+++ b/.github/workflows/chart-preview.yml
@@ -13,6 +13,11 @@ on:
required: false
default: false
type: boolean
+ plane-mcp-server:
+ description: "Plane MCP Server"
+ required: false
+ default: false
+ type: boolean
env:
PREVIEW_BUILD_FOLDER: helm-preview
@@ -28,6 +33,7 @@ env:
CHART_PREFIX: ${{ github.run_id }}
BUILD_PLANE_CE: ${{ github.event.inputs.plane-ce }}
BUILD_PLANE_EE: ${{ github.event.inputs.plane-enterprise }}
+ BUILD_PLANE_MCP_SERVER: ${{ github.event.inputs.plane-mcp-server }}
jobs:
build:
@@ -117,8 +123,28 @@ jobs:
cp charts/${{env.CHART_REPO}}/README.md ${{ env.EXPORT_DIR }}/${{env.CHART_REPO}}/${{env.CHART_REPO}}.md
helm repo index ${{ env.EXPORT_DIR }}/${{env.CHART_REPO}}
+ - id: build-plane-mcp-server
+ if: ${{ env.BUILD_PLANE_MCP_SERVER == 'true' }}
+ name: Build Plane-MCP-Server
+ working-directory: code
+ env:
+ EXPORT_DIR: ${{env.PREVIEW_BUILD_FOLDER}}
+ CHART_REPO: plane-mcp-server
+ CR_KEY: ${{ env.GPG_KEY_NAME }}
+ CR_PASSPHRASE_FILE: ${{env.GNUPGHOME}}/gpg-passphrase
+ CR_KEYRING: ${{env.GNUPGHOME}}/secring.gpg
+ run: |
+ flatBranchName=$(echo "${{ github.ref_name}}" | sed 's/\//\-/g')
+ sed -i "s/name: ${{env.CHART_REPO}}/name: ${{ env.CHART_PREFIX }}-${{env.CHART_REPO}}/" charts/${{env.CHART_REPO}}/Chart.yaml
+ sed -i "s/description: .*/description: ${flatBranchName}/g" charts/${{env.CHART_REPO}}/Chart.yaml
+ # sed -i "s/version: \(.*\)/version: \1-${flatBranchName}/" charts/${{env.CHART_REPO}}/Chart.yaml
+
+ helm package --sign --key "$CR_KEY" --keyring $CR_KEYRING --passphrase-file "$CR_PASSPHRASE_FILE" charts/$CHART_REPO -u -d ${{ env.EXPORT_DIR }}/${{env.CHART_REPO}}/charts
+ cp charts/${{env.CHART_REPO}}/README.md ${{ env.EXPORT_DIR }}/${{env.CHART_REPO}}/${{env.CHART_REPO}}.md
+ helm repo index ${{ env.EXPORT_DIR }}/${{env.CHART_REPO}}
+
- name: Publish
- if: ${{ env.BUILD_PLANE_CE == 'true' || env.BUILD_PLANE_EE == 'true' }}
+ if: ${{ env.BUILD_PLANE_CE == 'true' || env.BUILD_PLANE_EE == 'true' || env.BUILD_PLANE_MCP_SERVER == 'true' }}
working-directory: code
run: |
@@ -139,6 +165,11 @@ jobs:
Plane-Enterprise"
fi
+ if [ "${{ env.BUILD_PLANE_MCP_SERVER }}" == "true" ]; then
+ HTML_CONTENT="$HTML_CONTENT
+ Plane-MCP-Server"
+ fi
+
HTML_CONTENT="$HTML_CONTENT
"
echo $HTML_CONTENT >> ${{env.PREVIEW_BUILD_FOLDER}}/index.html
diff --git a/.github/workflows/chart-releaser.yml b/.github/workflows/chart-releaser.yml
index 30d45e4d..498c0a84 100644
--- a/.github/workflows/chart-releaser.yml
+++ b/.github/workflows/chart-releaser.yml
@@ -11,6 +11,10 @@ on:
description: 'Build Plane EE'
type: boolean
default: false
+ plane-mcp-server:
+ description: 'Build Plane MCP Server'
+ type: boolean
+ default: false
env:
CR_CONFIGFILE: "${{ github.workspace }}/cr.yaml"
@@ -23,13 +27,14 @@ env:
TARGET_BRANCH: "${{ github.ref_name }}"
CHART_NAME_CE: "plane-ce"
CHART_NAME_ENTERPRISE: "plane-enterprise"
+ CHART_NAME_MCP_SERVER: "plane-mcp-server"
MARK_AS_LATEST: true
MARK_AS_PRERELASE: false
PAGES_INDEX_PATH: ""
jobs:
setup:
- if: ${{ github.event.inputs.plane-ce == 'true' || github.event.inputs.plane-ee == 'true' }}
+ if: ${{ github.event.inputs.plane-ce == 'true' || github.event.inputs.plane-ee == 'true' || github.event.inputs.plane-mcp-server == 'true' }}
runs-on: ubuntu-22.04
permissions:
contents: write
@@ -81,6 +86,9 @@ jobs:
if [ "${{ github.event.inputs.plane-ee }}" = "false" ]; then
rm -rf charts/${{ env.CHART_NAME_ENTERPRISE }}
fi
+ if [ "${{ github.event.inputs.plane-mcp-server }}" = "false" ]; then
+ rm -rf charts/${{ env.CHART_NAME_MCP_SERVER }}
+ fi
- name: Rename Chart
if: github.ref_name != 'master'
@@ -94,6 +102,10 @@ jobs:
sed -i "s/name: \(.*\)/name: \1-${flatBranchName}/" charts/${{ env.CHART_NAME_ENTERPRISE }}/Chart.yaml
fi
+ if [ "${{ github.event.inputs.plane-mcp-server }}" = "true" ]; then
+ sed -i "s/name: \(.*\)/name: \1-${flatBranchName}/" charts/${{ env.CHART_NAME_MCP_SERVER }}/Chart.yaml
+ fi
+
echo "MARK_AS_LATEST=false" >> $GITHUB_ENV
echo "MARK_AS_PRERELASE=true" >> $GITHUB_ENV
echo "PAGES_INDEX_PATH=${flatBranchName}" >> $GITHUB_ENV
@@ -149,6 +161,9 @@ jobs:
if [ "${{ github.event.inputs.plane-ee }}" = "true" ]; then
cp code/charts/plane-enterprise/README.md pages/content/plane-ee.md
fi
+ if [ "${{ github.event.inputs.plane-mcp-server }}" = "true" ]; then
+ cp code/charts/plane-mcp-server/README.md pages/content/plane-mcp-server.md
+ fi
- name: Publish pages
working-directory: pages
diff --git a/charts/plane-mcp-server/.helmignore b/charts/plane-mcp-server/.helmignore
new file mode 100644
index 00000000..2d4a744c
--- /dev/null
+++ b/charts/plane-mcp-server/.helmignore
@@ -0,0 +1,24 @@
+# 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/
+test-*.yaml
\ No newline at end of file
diff --git a/charts/plane-mcp-server/Chart.yaml b/charts/plane-mcp-server/Chart.yaml
new file mode 100644
index 00000000..7ac88650
--- /dev/null
+++ b/charts/plane-mcp-server/Chart.yaml
@@ -0,0 +1,14 @@
+apiVersion: v2
+
+name: plane-mcp-server
+description: plane-mcp-server Setup
+
+type: application
+
+version: 1.0.0
+appVersion: "v0.2.0"
+
+home: https://plane.so
+icon: https://plane.so/favicon/favicon-32x32.png
+sources:
+ - https://github.com/makeplane/plane-mcp-server
\ No newline at end of file
diff --git a/charts/plane-mcp-server/README.md b/charts/plane-mcp-server/README.md
new file mode 100644
index 00000000..fc41aecd
--- /dev/null
+++ b/charts/plane-mcp-server/README.md
@@ -0,0 +1,113 @@
+# Plane MCP Server Helm Chart
+
+## Pre-requisite
+
+- A working Kubernetes cluster
+- `kubectl` and `helm` on the client system that you will use to install our Helm charts
+
+## Installing Plane MCP Server
+
+1. Open Terminal or any other command-line app that has access to Kubernetes tools on your local system.
+
+1. Set-up and customization
+
+ For more control over your set-up, extract the Helm chart to access the values file and edit using any editor like Vim or Nano.
+
+ ```bash
+ # Extract the Helm chart to access the values file
+ helm show values plane-mcp-server --repo https://private-helm.plane.tools > custom-values.yaml
+ vi custom-values.yaml
+ ```
+
+ > See `Configuration Settings` for more details.
+
+ After saving the `custom-values.yaml` file, continue to be on the same Terminal window as on the previous steps, copy the code below, and paste it on your Terminal screen.
+
+ ```bash
+ helm upgrade plane-mcp-server-app plane-mcp-server \
+ --repo https://private-helm.plane.tools \
+ --install \
+ --create-namespace \
+ --namespace plane-mcp-server \
+ -f custom-values.yaml \
+ --timeout 10m \
+ --wait \
+ --wait-for-jobs
+ ```
+
+## Configuration Settings
+
+### Docker Registry Configuration
+
+| Setting | Default | Required | Description |
+| ---------------------------- | :---------------------------: | :------: | --------------------------------------------------------------------------- |
+| dockerRegistry.enabled | true | | Enable Docker registry authentication for pulling images |
+| dockerRegistry.loginid | planeengineering | | Docker registry login ID/username |
+| dockerRegistry.password | | | Docker registry password or token |
+| dockerRegistry.default_tag | latest | | Default image tag for MCP server image |
+| services.api.image | makeplane/plane-mcp-server | | MCP Server Docker image name (without tag) |
+
+### MCP Server Setup
+
+| Setting | Default | Required | Description |
+| ---------------------------- | :--------------------: | :------: | --------------------------------------------------------------------------- |
+| services.api.replicas | 1 | | Number of MCP Server replicas |
+| services.api.memoryLimit | 1000Mi | | Memory limit for MCP Server pods |
+| services.api.cpuLimit | 500m | | CPU limit for MCP Server pods |
+| services.api.memoryRequest | 50Mi | | Memory request for MCP Server pods |
+| services.api.cpuRequest | 50m | | CPU request for MCP Server pods |
+
+### Plane OAuth Configuration
+
+| Setting | Default | Required | Description |
+| ------------------------------------- | :---------: | :------: | --------------------------------------------------------------------------- |
+| services.api.plane_oauth.client_id | | Yes | Plane OAuth Client ID for authentication |
+| services.api.plane_oauth.client_secret| | Yes | Plane OAuth Client Secret for authentication |
+| services.api.plane_oauth.redirect_uri | | Yes | OAuth redirect URI for callback handling |
+| services.api.plane_oauth.base_url | | Yes | Plane instance base URL for OAuth |
+
+### Redis/Valkey Setup
+
+| Setting | Default | Required | Description |
+| -------------------------------- | :-------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| services.redis.local_setup | true | | Plane MCP Server uses `redis` to cache session data. This can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely. Set this to `true` when you choose to setup stateful deployment of `redis`. Mark it as `false` when using a remotely hosted database |
+| services.redis.image | valkey/valkey:7.2.11-alpine | | Using this key, user must provide the docker image name to setup the stateful deployment of `redis`. (must be set when `services.redis.local_setup=true`) |
+| services.redis.volume_size | 500Mi | | While setting up the stateful deployment, while creating the persistent volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) |
+| services.redis.external_redis_url| | | Users can also decide to use the remote hosted database and link to Plane MCP Server deployment. Ignoring all the above keys, set `services.redis.local_setup` to `false` and set this key with remote connection url |
+
+### Ingress Configuration
+
+| Setting | Default | Required | Description |
+| -------------------------------- | :-----------------------------------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| ingress.enabled | true | | Enable ingress for Plane MCP Server |
+| ingress.host | mcp.example.com | Yes | Main hostname for Plane MCP Server application |
+| ingress.ingressClass | nginx | Yes | Kubernetes cluster setup comes with various options of `ingressClass`. Based on your setup, set this value to the right one (eg. nginx, traefik, etc). Leave it to default in case you are using external ingress provider |
+| ingress.ingressAnnotations | { nginx.ingress.kubernetes.io/proxy-body-size: "10m" } | | Ingress controllers comes with various configuration options which can be passed as annotations. Setting this value lets you change the default value to user required |
+| ingress.ssl.enabled | false | | Enable SSL/TLS for ingress |
+| ingress.ssl.issuer | cloudflare | | CertManager configuration allows user to create issuers using `http` or any of the other DNS Providers like `cloudflare`, `digitalocean`, etc. As of now Plane MCP Server supports `http`, `cloudflare`, `digitalocean` |
+| ingress.ssl.token | | | To create issuers using DNS challenge, set the issuer api token of dns provider like `cloudflare` or `digitalocean` (not required for http) |
+| ingress.ssl.server | https://acme-v02.api.letsencrypt.org/directory | | Issuer creation configuration need the certificate generation authority server url. Default URL is the `Let's Encrypt` server |
+| ingress.ssl.email | engineering@plane.so | | Certificate generation authority needs a valid email id before generating certificate. Required when `ingress.ssl.enabled=true` |
+
+## Custom Ingress Routes
+
+If you are planning to use 3rd party ingress providers, here is the available route configuration
+
+| Host | Path | Service | Required |
+| ----------------------- | :-----------: | ------------------------------------------ | :------: |
+| mcp.example.com | / | -api:8000> | Yes |
+
+## Verify
+
+- After install, the MCP Server listens on Service `-api` port 8000
+- If ingress is enabled, access the application at `https:///` via Ingress
+- Check all pods are running: `kubectl get pods -n `
+- Check services: `kubectl get svc -n `
+
+## Troubleshooting
+
+- Ensure `ingress.host` resolves to your ingress controller
+- For TLS issues, check cert-manager events and Issuer/Certificate resources in the install namespace
+- If using external Redis, verify `services.redis.external_redis_url` is reachable from the cluster
+- Confirm Redis/Valkey pods are Ready; caching depends on it
+- Ensure all Plane OAuth configuration values are correctly set (client_id, client_secret, redirect_uri, base_url)
diff --git a/charts/plane-mcp-server/questions.yml b/charts/plane-mcp-server/questions.yml
new file mode 100644
index 00000000..a33f1bee
--- /dev/null
+++ b/charts/plane-mcp-server/questions.yml
@@ -0,0 +1,139 @@
+questions:
+
+- variable: dockerRegistry.loginid
+ label: "Login ID"
+ type: string
+ default: "makeplane"
+ group: "Docker Credentials"
+ subquestions:
+ - variable: dockerRegistry.password
+ label: "Password/Token"
+ type: password
+ default: ""
+ - variable: dockerRegistry.default_tag
+ label: "Image Tag"
+ type: string
+ default: "latest"
+ - variable: services.api.image
+ label: "MCP Server Docker Image (without tag)"
+ type: string
+ default: "makeplane/plane-mcp-server"
+ - variable: services.storage_class
+ label: "Storage Class"
+ type: storageclass
+
+- variable: services.api.replicas
+ label: "MCP Server Replicas"
+ type: int
+ default: 1
+ group: "MCP Server Setup"
+ subquestions:
+ - variable: services.api.memoryLimit
+ label: "Memory Limit"
+ type: string
+ default: "1000Mi"
+ - variable: services.api.cpuLimit
+ label: "CPU Limit"
+ type: string
+ default: "500m"
+ - variable: services.api.memoryRequest
+ label: "Memory Request"
+ type: string
+ default: "50Mi"
+ - variable: services.api.cpuRequest
+ label: "CPU Request"
+ type: string
+ default: "50m"
+
+- variable: services.api.plane_oauth.enabled
+ label: "Enable Plane OAuth"
+ type: boolean
+ default: true
+ group: "MCP Server Setup"
+ show_subquestion_if: true
+ subquestions:
+ - variable: services.api.plane_oauth.client_id
+ label: "Plane OAuth Client ID"
+ type: string
+ default: ""
+ - variable: services.api.plane_oauth.client_secret
+ label: "Plane OAuth Client Secret"
+ type: password
+ default: ""
+ - variable: services.api.plane_oauth.provider_base_url
+ label: "Plane OAuth Provider Base URL"
+ type: string
+ default: ""
+ - variable: services.api.plane_base_url
+ label: "Plane Base URL"
+ type: string
+ default: ""
+ - variable: services.api.plane_internal_base_url
+ label: "Plane Internal Base URL"
+ type: string
+ default: ""
+
+- variable: services.redis.local_setup
+ label: "Install Redis"
+ type: boolean
+ default: true
+ group: "Redis Setup"
+ subquestions:
+ - variable: services.redis.image
+ label: "Docker Image"
+ type: string
+ default: "valkey/valkey:7.2.11-alpine"
+ show_if: "services.redis.local_setup=true"
+ - variable: services.redis.volume_size
+ label: "Volume Size"
+ type: string
+ default: "500Mi"
+ show_if: "services.redis.local_setup=true"
+ - variable: services.redis.external_redis_url
+ label: "Remote Redis URL"
+ type: string
+ default: "redis://"
+ show_if: "services.redis.local_setup=false"
+
+- variable: ingress.host
+ label: "MCP Server Hostname"
+ type: string
+ required: true
+ default: "mcp.example.com"
+ group: "Ingress Setup"
+ subquestions:
+ - variable: ingress.ingressClass
+ label: "Ingress Classname"
+ type: string
+ required: true
+ default: "nginx"
+
+- variable: ingress.ssl.enabled
+ label: "Enable SSL"
+ type: boolean
+ default: false
+ group: "Ingress Setup"
+ show_subquestion_if: true
+ subquestions:
+ - variable: ingress.ssl.issuer
+ label: "SSL Issuer"
+ type: enum
+ options:
+ - "http"
+ - "cloudflare"
+ - "digitalocean"
+ default: "cloudflare"
+ - variable: ingress.ssl.server
+ label: "SSL Server"
+ type: string
+ default: "https://acme-v02.api.letsencrypt.org/directory"
+ - variable: ingress.ssl.email
+ label: "SSL Email"
+ type: string
+ default: "engineering@plane.so"
+ - variable: ingress.ssl.token
+ label: "Provider API Token"
+ type: password
+ default: ""
+ description: "Not required for 'http' issuer"
+ show_if: "ingress.ssl.issuer=cloudflare || ingress.ssl.issuer=digitalocean"
diff --git a/charts/plane-mcp-server/templates/_helpers.tpl b/charts/plane-mcp-server/templates/_helpers.tpl
new file mode 100644
index 00000000..0aa026f6
--- /dev/null
+++ b/charts/plane-mcp-server/templates/_helpers.tpl
@@ -0,0 +1,3 @@
+{{- define "imagePullSecret" }}
+{{- printf "{\"auths\":{\"index.docker.io/v1/\":{\"username\":\"%s\",\"password\":\"%s\"}}}" .Values.dockerRegistry.loginid .Values.dockerRegistry.password | b64enc }}
+{{- end }}
\ No newline at end of file
diff --git a/charts/plane-mcp-server/templates/config-secrets/app-env.yaml b/charts/plane-mcp-server/templates/config-secrets/app-env.yaml
new file mode 100644
index 00000000..20185d9a
--- /dev/null
+++ b/charts/plane-mcp-server/templates/config-secrets/app-env.yaml
@@ -0,0 +1,20 @@
+apiVersion: v1
+kind: Secret
+type: Opaque
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-app-secrets
+stringData:
+ PLANE_OAUTH_PROVIDER_CLIENT_ID: {{ .Values.services.api.plane_oauth.client_id | quote }}
+ PLANE_OAUTH_PROVIDER_CLIENT_SECRET: {{ .Values.services.api.plane_oauth.client_secret | quote }}
+ PLANE_OAUTH_PROVIDER_BASE_URL: {{ .Values.services.api.plane_oauth.provider_base_url | quote }}
+ PLANE_BASE_URL: {{ .Values.services.api.plane_base_url | quote }}
+ PLANE_INTERNAL_BASE_URL: {{ .Values.services.api.plane_internal_base_url | quote }}
+ REDIS_HOST: {{ .Release.Name }}-redis
+ REDIS_PORT: "6379"
+ {{- if .Values.services.redis.local_setup }}
+ REDIS_URL: "redis://{{ .Release.Name }}-redis.{{ .Release.Namespace }}.svc.cluster.local:6379/"
+ {{- else }}
+ REDIS_URL: {{ .Values.services.redis.external_redis_url | default "" | quote }}
+ {{- end }}
+ PORT: "8211"
\ No newline at end of file
diff --git a/charts/plane-mcp-server/templates/config-secrets/docker-registry.yaml b/charts/plane-mcp-server/templates/config-secrets/docker-registry.yaml
new file mode 100644
index 00000000..14e6b3dc
--- /dev/null
+++ b/charts/plane-mcp-server/templates/config-secrets/docker-registry.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.dockerRegistry.enabled }}
+
+apiVersion: v1
+kind: Secret
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-docker-registry-credentials
+data:
+ .dockerconfigjson: {{ include "imagePullSecret" .}}
+type: kubernetes.io/dockerconfigjson
+
+{{- end }}
\ No newline at end of file
diff --git a/charts/plane-mcp-server/templates/ingress/ingress.yaml b/charts/plane-mcp-server/templates/ingress/ingress.yaml
new file mode 100644
index 00000000..3a00be35
--- /dev/null
+++ b/charts/plane-mcp-server/templates/ingress/ingress.yaml
@@ -0,0 +1,30 @@
+{{- if and .Values.ingress.enabled .Values.ingress.host }}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-ingress
+ annotations:
+ nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.ingress.clientMaxBodySize | default "5m" | quote}}
+spec:
+ ingressClassName: {{ .Values.ingress.ingressClass }}
+ rules:
+ - host: {{ .Values.ingress.host }}
+ http:
+ paths:
+ - backend:
+ service:
+ port:
+ number: 8211
+ name: {{ .Release.Name }}-api
+ path: /
+ pathType: Prefix
+
+ {{- if .Values.ingress.ssl.enabled }}
+ tls:
+ - hosts:
+ - {{ .Values.ingress.host | quote }}
+ secretName: {{ .Release.Name }}-ssl-cert
+ {{- end }}
+
+{{- end }}
\ No newline at end of file
diff --git a/charts/plane-mcp-server/templates/ingress/issuers-certs.yaml b/charts/plane-mcp-server/templates/ingress/issuers-certs.yaml
new file mode 100644
index 00000000..979e66d7
--- /dev/null
+++ b/charts/plane-mcp-server/templates/ingress/issuers-certs.yaml
@@ -0,0 +1,59 @@
+{{- if and .Values.ingress.enabled .Values.ingress.ssl.enabled .Values.ingress.host }}
+
+{{- if ne .Values.ingress.ssl.issuer "http" }}
+apiVersion: v1
+kind: Secret
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-issuer-api-token-secret
+type: Opaque
+stringData:
+ api-token: {{ .Values.ingress.ssl.token | default "default-api-token" | quote }}
+
+---
+{{- end }}
+
+apiVersion: cert-manager.io/v1
+kind: Issuer
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-cert-issuer
+spec:
+ acme:
+ email: {{ .Values.ingress.ssl.email }}
+ server: {{ .Values.ingress.ssl.server }}
+ privateKeySecretRef:
+ name: {{ .Release.Name }}-cert-issuer-key
+ solvers:
+ {{- if eq .Values.ingress.ssl.issuer "cloudflare" }}
+ - dns01:
+ cloudflare:
+ apiTokenSecretRef:
+ name: {{ .Release.Name }}-issuer-api-token-secret
+ key: api-token
+ {{- else if eq .Values.ingress.ssl.issuer "digitalocean" }}
+ - dns01:
+ digitalocean:
+ tokenSecretRef:
+ name: {{ .Release.Name }}-issuer-api-token-secret
+ key: api-token
+ {{- else if eq .Values.ingress.ssl.issuer "http" }}
+ - http01:
+ ingress:
+ ingressClassName: {{ .Values.ingress.ingressClass }}
+ {{- end }}
+
+---
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-ssl-cert
+spec:
+ dnsNames:
+ - {{ .Values.ingress.host | quote }}
+ issuerRef:
+ name: {{ .Release.Name }}-cert-issuer
+ secretName: {{ .Release.Name }}-ssl-cert
+
+{{- end}}
diff --git a/charts/plane-mcp-server/templates/service-account.yaml b/charts/plane-mcp-server/templates/service-account.yaml
new file mode 100644
index 00000000..6eed2f5f
--- /dev/null
+++ b/charts/plane-mcp-server/templates/service-account.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+automountServiceAccountToken: true
+kind: ServiceAccount
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-srv-account
+{{- if .Values.dockerRegistry.enabled }}
+imagePullSecrets:
+ - name: {{ .Release.Name }}-docker-registry-credentials
+{{- end}}
\ No newline at end of file
diff --git a/charts/plane-mcp-server/templates/workloads/plane-mcp-server.deployment.yaml b/charts/plane-mcp-server/templates/workloads/plane-mcp-server.deployment.yaml
new file mode 100644
index 00000000..f06f42d1
--- /dev/null
+++ b/charts/plane-mcp-server/templates/workloads/plane-mcp-server.deployment.yaml
@@ -0,0 +1,58 @@
+
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-api
+ labels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-api
+spec:
+ clusterIP: None
+ ports:
+ - name: api-8211
+ port: 8211
+ protocol: TCP
+ targetPort: 8211
+ selector:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-api
+
+---
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-api-wl
+spec:
+ replicas: {{ .Values.services.api.replicas | default 1}}
+ selector:
+ matchLabels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-api
+ template:
+ metadata:
+ namespace: {{ .Release.Namespace }}
+ labels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-api
+ annotations:
+ timestamp: {{ now | quote }}
+ spec:
+ containers:
+ - name: {{ .Release.Name }}-api
+ imagePullPolicy: Always
+ image: {{ .Values.services.api.image | default "makeplane/plane-mcp-server" }}:{{ .Values.dockerRegistry.default_tag | default "latest" }}
+ stdin: true
+ tty: true
+ resources:
+ requests:
+ memory: {{ .Values.services.api.memoryRequest | default "50Mi" | quote }}
+ cpu: {{ .Values.services.api.cpuRequest | default "50m" | quote }}
+ limits:
+ memory: {{ .Values.services.api.memoryLimit | default "1000Mi" | quote }}
+ cpu: {{ .Values.services.api.cpuLimit | default "500m" | quote }}
+ envFrom:
+ - secretRef:
+ name: {{ .Release.Name }}-app-secrets
+ optional: false
+ serviceAccount: {{ .Release.Name }}-srv-account
+ serviceAccountName: {{ .Release.Name }}-srv-account
+---
\ No newline at end of file
diff --git a/charts/plane-mcp-server/templates/workloads/redis-statefulset.yaml b/charts/plane-mcp-server/templates/workloads/redis-statefulset.yaml
new file mode 100644
index 00000000..84e9b7e2
--- /dev/null
+++ b/charts/plane-mcp-server/templates/workloads/redis-statefulset.yaml
@@ -0,0 +1,66 @@
+{{- if .Values.services.redis.local_setup }}
+
+apiVersion: v1
+kind: Service
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-redis
+ labels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-redis
+spec:
+ clusterIP: None
+ ports:
+ - name: redis-6379
+ port: 6379
+ protocol: TCP
+ targetPort: 6379
+ selector:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-redis
+---
+
+# REDIS WORKLOAD
+
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ namespace: {{ .Release.Namespace }}
+ name: {{ .Release.Name }}-redis-wl
+spec:
+ selector:
+ matchLabels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-redis
+ serviceName: {{ .Release.Name }}-redis
+ template:
+ metadata:
+ labels:
+ app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-redis
+ spec:
+ containers:
+ - image: {{ .Values.services.redis.image }}
+ imagePullPolicy: Always
+ name: {{ .Release.Name }}-redis
+ stdin: true
+ tty: true
+ volumeMounts:
+ - mountPath: /data
+ name: pvc-{{ .Release.Name }}-redis-vol
+ subPath: ''
+ serviceAccount: {{ .Release.Name }}-srv-account
+ serviceAccountName: {{ .Release.Name }}-srv-account
+ volumeClaimTemplates:
+ - apiVersion: v1
+ kind: PersistentVolumeClaim
+ metadata:
+ creationTimestamp: null
+ namespace: {{ .Release.Namespace }}
+ name: pvc-{{ .Release.Name }}-redis-vol
+ spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: {{ .Values.services.redis.volume_size | default "1Gi" | quote }}
+ storageClassName: {{ .Values.services.storage_class | quote }}
+ volumeMode: Filesystem
+
+{{- end }}
\ No newline at end of file
diff --git a/charts/plane-mcp-server/values.yaml b/charts/plane-mcp-server/values.yaml
new file mode 100644
index 00000000..a9659178
--- /dev/null
+++ b/charts/plane-mcp-server/values.yaml
@@ -0,0 +1,40 @@
+dockerRegistry:
+ enabled: true
+ loginid: makeplane
+ password: ''
+ default_tag: latest
+
+ingress:
+ enabled: true
+ host: 'mcp.example.com'
+ ingressClass: 'nginx'
+ ingressAnnotations: { nginx.ingress.kubernetes.io/proxy-body-size: "10m" }
+ ssl:
+ enabled: false
+ issuer: cloudflare # Allowed : cloudflare, digitalocean, http
+ token: '' # not required for http
+ server: https://acme-v02.api.letsencrypt.org/directory
+ email: engineering@plane.so
+
+services:
+ storage_class: ''
+ api:
+ image: makeplane/plane-mcp-server
+ replicas: 1
+ memoryLimit: 1000Mi
+ cpuLimit: 500m
+ memoryRequest: 50Mi
+ cpuRequest: 50m
+ plane_base_url: ''
+ plane_internal_base_url: ''
+ plane_oauth:
+ enabled: false
+ client_id: ''
+ client_secret: ''
+ provider_base_url: ''
+
+ redis:
+ local_setup: true
+ image: valkey/valkey:7.2.11-alpine
+ volume_size: 500Mi
+ external_redis_url: '' # INCASE OF REMOTE REDIS ONLY
diff --git a/test.sh b/test.sh
index 683cfb9a..f2879b72 100755
--- a/test.sh
+++ b/test.sh
@@ -29,6 +29,7 @@ HELM_CHART=$(dialog \
--menu "Select the Helm Chart to test" 25 50 20 \
"1" "Plane-CE" \
"2" "Plane-Enterprise" \
+ "3" "Plane-MCP-Server" \
3>&1 1>&2 2>&3)
@@ -49,6 +50,15 @@ elif [ "$HELM_CHART" == "2" ]; then
else
printFailed
fi
+elif [ "$HELM_CHART" == "3" ]; then
+ helm template plane-mcp-server-app-$(date +%s) charts/plane-mcp-server -n myns > test-mcp-server.yaml
+ if [ $? -eq 0 ]; then
+ clear
+ printSuccess
+ code test-mcp-server.yaml
+ else
+ printFailed
+ fi
else
exit 0
fi