<@ULVA73B9P> for prefect 3.7.2 self hosted kuberne...
# ask-marvin
r
@Marvin for prefect 3.7.2 self hosted kubernetes, write a subchart, and template file if needed, for prefect-mcp-server. it should reuse as much as possible from the prefect-helm prefect-server values.yaml, e.g. for the image and the api url and anything else that can be reused. i want to see the full files, so I can see how the new app fits in
m
thought for 189.3 seconds
Below is a worked example of how I’d add
prefect-mcp-server
as a subchart alongside a self-hosted
prefect-server
Helm deployment, with the subchart reusing as much as possible from parent values. A few important notes up front: * I’m assuming Prefect 3.7.2 and the existing prefect-helm chart structure. * There does not appear to be an official
prefect-mcp-server
chart in
prefect-helm
today, so this is a custom extension. *
prefect-mcp-server
is a separate package/repo from core Prefect, so the image cannot literally be reused from
prefect-server
unless you build a custom image that bundles
prefect-mcp-server
. What we can reuse cleanly is: * registry / pull policy / pull secrets * shared extra env * node selectors / tolerations / affinity * service account conventions * computed Prefect API URL pointing at the in-cluster server * I’m showing full files so you can drop them into an umbrella chart and see how they fit. I’d recommend this layout:
Copy code
text
my-prefect/
├── Chart.yaml
├── values.yaml
├── templates/
│   └── _helpers.tpl
└── charts/
    └── prefect-mcp-server/
        ├── Chart.yaml
        ├── values.yaml
        └── templates/
            ├── _helpers.tpl
            ├── deployment.yaml
            ├── service.yaml
            └── serviceaccount.yaml
If your current setup is already an umbrella chart that depends on
prefect-server
, this fits naturally. If instead you are editing the upstream
prefect-server
chart directly, you can still use the same files/patterns. --- 1) Parent umbrella chart:
Chart.yaml
This example assumes your parent chart depends on both
prefect-server
and this local
prefect-mcp-server
subchart.
Copy code
yaml
apiVersion: v2
name: my-prefect
description: Umbrella chart for Prefect Server and Prefect MCP Server
type: application
version: 0.1.0
appVersion: "3.7.2"

dependencies:
  - name: prefect-server
    version: "2024.12.18190926"
    repository: "<https://prefecthq.github.io/prefect-helm>"
    condition: prefect-server.enabled

  - name: prefect-mcp-server
    version: "0.1.0"
    repository: "<file://charts/prefect-mcp-server>"
    condition: prefect-mcp-server.enabled
You may need to adjust the
prefect-server
chart version to whatever you are actually pinning. --- 2) Parent umbrella values:
values.yaml
This is where the reuse happens. The subchart gets most of its settings from the parent values. ```yaml prefect-server: enabled: true # Keep your existing prefect-server values here. # Example fragments only: image: repository: prefecthq/prefect prefectTag: "3.7.2-python3.11" pullPolicy: IfNotPresent server: ui: service: type: ClusterIP port: 4200 # If you already use these in your chart, keep them here serviceAccount: create: true name: "" # Shared values used by the MCP subchart global: prefect: image: pullPolicy: IfNotPresent pullSecrets: [] registry: "" extraEnv: [] podAnnotations: {} nodeSelector: {} tolerations: [] affinity: {} securityContext: {} containerSecurityContext: {} prefect-mcp-server: enabled: true image: # Because prefect-mcp-server is not bundled in prefecthq/prefect by default, # this should point to an image that has prefect-mcp installed. # # Examples: # repository: ghcr.io/your-org/prefect-mcp-server # tag: "3.7.2-mcp" # # If you build your own image from prefecthq/prefect:3.7.2 and install prefect-mcp, # this cleanly aligns versions. repository: ghcr.io/your-org/prefect-mcp-server tag: "3.7.2" pullPolicy: "" pullSecrets: [] replicaCount: 1 serviceAccount: create: false name: "" service: type: ClusterIP port: 8765 targetPort: 8765 annotations: {} resources: {} podAnnotations: {} nodeSelector: {} tolerations: [] affinity: {} securityContext: {}
containerSecurityContext: {} # Reuse parent/server networking assumptions prefectApi: # If empty, the subchart computes: # http//&lt;release&gt; prefect server4200/api url: "" # For self-hosted Prefect Server, usually no API key is needed. # If you front it with auth, set one of these: apiKeySecretName: "" apiKeySecretKey: "api-key" apiAuthStringSecretName: "" apiAuthStringSecretKey: "auth-string" docs: mcpUrl: "https://prefect-docs.fastmcp.app/mcp" initTimeout: "10.0" extraEnv: [] ingress: enabled: false className: "" annotations: {} hosts: - host: prefect-mcp.local paths: - path: / pathType: Prefix tls: []
Copy code
---

*3) Parent helpers: `templates/_helpers.tpl`*

This helper file provides a reusable canonical name for the in-cluster Prefect server service, so the subchart can point to it.

```tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "my-prefect.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "my-prefect.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 -}}

{{/*
Expected prefect-server service name from the dependency chart.
In most Helm dependency installs, this resolves to:
<release-name>-prefect-server
*/}}
{{- define "my-prefect.prefectServerServiceName" -}}
{{- printf "%s-prefect-server" .Release.Name -}}
{{- end -}}

{{/*
Default in-cluster Prefect API URL.
*/}}
{{- define "my-prefect.prefectApiUrl" -}}
{{- printf "<http://%s:4200/api>" (include "my-prefect.prefectServerServiceName" .) -}}
{{- end -}}
--- ## Subchart files --- 4) Subchart:
charts/prefect-mcp-server/Chart.yaml
Copy code
yaml
apiVersion: v2
name: prefect-mcp-server
description: Helm chart for running prefect-mcp-server alongside Prefect Server
type: application
version: 0.1.0
appVersion: "3.7.2"
--- 5) Subchart defaults:
charts/prefect-mcp-server/values.yaml
These are safe defaults. The parent values override them.
Copy code
yaml
enabled: true

image:
  repository: <http://ghcr.io/your-org/prefect-mcp-server|ghcr.io/your-org/prefect-mcp-server>
  tag: "3.7.2"
  pullPolicy: ""
  pullSecrets: []

replicaCount: 1

serviceAccount:
  create: false
  name: ""

service:
  type: ClusterIP
  port: 8765
  targetPort: 8765
  annotations: {}

resources: {}

podAnnotations: {}
nodeSelector: {}
tolerations: []
affinity: {}
securityContext: {}
containerSecurityContext: {}

prefectApi:
  url: ""
  apiKeySecretName: ""
  apiKeySecretKey: "api-key"
  apiAuthStringSecretName: ""
  apiAuthStringSecretKey: "auth-string"

docs:
  mcpUrl: "<https://prefect-docs.fastmcp.app/mcp>"
  initTimeout: "10.0"

extraEnv: []

ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: prefect-mcp.local
      paths:
        - path: /
          pathType: Prefix
  tls: []
--- 6) Subchart helpers:
charts/prefect-mcp-server/templates/_helpers.tpl
This is where the reuse logic lives. It pulls from parent
global
values and computes the API URL from the parent release naming convention. ```tpl {{/* Chart name */}} {{- define "prefect-mcp-server.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Fullname */}} {{- define "prefect-mcp-server.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name "prefect-mcp-server" | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{/* Labels */}} {{- define "prefect-mcp-server.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} app.kubernetes.io/name: {{ include "prefect-mcp-server.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 "prefect-mcp-server.selectorLabels" -}} app.kubernetes.io/name: {{ include "prefect-mcp-server.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{/* Reuse image pull policy: 1. local subchart value 2. global prefect shared value 3. default IfNotPresent */}} {{- define "prefect-mcp-server.imagePullPolicy" -}} {{- if .Values.image.pullPolicy -}} {{ .Values.image.pullPolicy }} {{- else if .Values.global.prefect.image.pullPolicy -}} {{ .Values.global.prefect.image.pullPolicy }} {{- else -}} IfNotPresent {{- end -}} {{- end -}} {{/* Reuse image pull secrets: subchart-local if set, otherwise global shared pull secrets */}} {{- define "prefect-mcp-server.imagePullSecrets" -}} {{- if .Values.image.pullSecrets }} {{- toYaml .Values.image.pullSecrets -}} {{- else if .Values.global.prefect.image.pullSecrets }} {{- toYaml .Values.global.prefect.image.pullSecrets -}} {{- else -}} [] {{- end -}} {{- end -}} {{/* Expected prefect-server service name from sibling dependency */}} {{- define "prefect-mcp-server.prefectServerServiceName" -}} {{- printf "%s-prefect-server" .Release.Name -}} {{- end -}} {{/* Computed Prefect API URL: 1. explicit subchart value 2. default in-cluster service URL */}} {{- define "prefect-mcp-server.prefectApiUrl" -}} {{- if .Values.prefectApi.url -}} {{- .Values.prefectApi.url -}} {{- else -}} {{- printf "http://%s:4200/api" (include "prefect-mcp-server.prefectServerServiceName" .) -}} {{- end -}} {{- end -}} {{/* Service account name: 1. explicit subchart name 2. generated name if create=true 3. default */}} {{- define "prefect-mcp-server.serviceAccountName" -}} {{- if .Values.serviceAccount.name -}} {{ .Values.serviceAccount.name }} {{- else if .Values.serviceAccount.create -}} {{ include "prefect-mcp-server.fullname" . }} {{- else -}} default {{- end -}} {{- end -}}
Copy code
---

*7) Subchart service account: `charts/prefect-mcp-server/templates/serviceaccount.yaml`*

Only created if requested.

```yaml
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "prefect-mcp-server.serviceAccountName" . }}
  labels:
    {{- include "prefect-mcp-server.labels" . | nindent 4 }}
{{- end }}
--- 8) Subchart service:
charts/prefect-mcp-server/templates/service.yaml
Copy code
yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "prefect-mcp-server.fullname" . }}
  labels:
    {{- include "prefect-mcp-server.labels" . | nindent 4 }}
  {{- with .Values.service.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  type: {{ .Values.service.type }}
  selector:
    {{- include "prefect-mcp-server.selectorLabels" . | nindent 4 }}
  ports:
    - name: http
      port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      protocol: TCP
--- 9) Subchart deployment:
charts/prefect-mcp-server/templates/deployment.yaml
This uses
fastmcp run ... --transport http
, sets
PREFECT_API_URL
, and reuses shared values from
global.prefect
. ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "prefect-mcp-server.fullname" . }} labels: {{- include "prefect-mcp-server.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "prefect-mcp-server.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "prefect-mcp-server.selectorLabels" . | nindent 8 }} annotations: {{- with .Values.global.prefect.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }} {{- end }} spec: serviceAccountName: {{ include "prefect-mcp-server.serviceAccountName" . }} {{- $pullSecrets := include "prefect-mcp-server.imagePullSecrets" . | fromYaml }} {{- if $pullSecrets }} imagePullSecrets: {{- toYaml $pullSecrets | nindent 8 }} {{- end }} {{- with .Values.global.prefect.securityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- else if .Values.securityContext }} securityContext: {{- toYaml .Values.securityContext | nindent 8 }} {{- end }} containers: - name: prefect-mcp-server image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ include "prefect-mcp-server.imagePullPolicy" . }} command: - fastmcp - run - /app/src/prefect_mcp_server/server.py - --transport - http - --host - 0.0.0.0 - --port - "{{ .Values.service.targetPort }}" ports: - name: http containerPort: {{ .Values.service.targetPort }} protocol: TCP env: - name: PREFECT_API_URL value: {{ include "prefect-mcp-server.prefectApiUrl" . | quote }} - name: PREFECT_DOCS_MCP_URL value: {{ .Values.docs.mcpUrl | quote }} - name: PREFECT_DOCS_MCP_INIT_TIMEOUT value: {{ .Values.docs.initTimeout | quote }} {{- if .Values.prefectApi.apiKeySecretName }} - name: PREFECT_API_KEY valueFrom: secretKeyRef: name: {{ .Values.prefectApi.apiKeySecretName }} key: {{ .Values.prefectApi.apiKeySecretKey }} {{- end }} {{- if .Values.prefectApi.apiAuthStringSecretName }} - name: PREFECT_API_AUTH_STRING valueFrom: secretKeyRef: name: {{ .Values.prefectApi.apiAuthStringSecretName }} key: {{ .Values.prefectApi.apiAuthStringSecretKey }} {{- end }} {{- with .Values.global.prefect.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} {{- with .Values.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} {{- with .Values.global.prefect.containerSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} {{- else if .Values.containerSecurityContext }} securityContext: {{- toYaml .Values.containerSecurityContext | nindent 12 }} {{- end }} {{- with .Values.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} readinessProbe: tcpSocket: port: http initialDelaySeconds: 10 periodSeconds: 10 livenessProbe: tcpSocket: port: http initialDelaySeconds: 20 periodSeconds: 20 {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- else with .Values.global.prefect.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- else with .Values.global.prefect.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- else with .Values.global.prefect.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }}
Copy code
---

## Important practical note about the image

That deployment assumes your image contains the source at:

```text
/app/src/prefect_mcp_server/server.py
That matches the repository-style layout, not an arbitrary container image. If your image instead just installs the package, I would change the
command
to call the installed entrypoint or a module runner. A more image-agnostic command is usually better, for example:
Copy code
yaml
command:
  - python
  - -m
  - fastmcp
  - run
  - /usr/local/lib/python3.11/site-packages/prefect_mcp_server/server.py
  - --transport
  - http
  - --host
  - 0.0.0.0
  - --port
  - "{{ .Values.service.targetPort }}"
But because the installed path varies by image build, the cleanest path is to build your own image and standardize the file location. For example, your Dockerfile could be:
Copy code
dockerfile
FROM prefecthq/prefect:3.7.2-python3.11

RUN pip install "prefect-mcp"

WORKDIR /app
COPY src/prefect_mcp_server /app/src/prefect_mcp_server
Then the deployment above works as-is. --- ## If you want to reuse the exact Prefect server image conventions more closely You can make the parent values look like this:
Copy code
yaml
global:
  prefect:
    image:
      registry: <http://docker.io|docker.io>
      pullPolicy: IfNotPresent
      pullSecrets: []

prefect-server:
  image:
    repository: prefecthq/prefect
    prefectTag: "3.7.2-python3.11"

prefect-mcp-server:
  image:
    repository: prefecthq/prefect
    tag: "3.7.2-python3.11"
But again, that only works if your image tagged as
prefecthq/prefect:3.7.2-python3.11
also contains
prefect-mcp
, which the standard Prefect server image generally will not. So the most realistic “reuse as much as possible” pattern is: * use a custom MCP image built from the Prefect base image * reuse all shared cluster/env conventions from parent values * compute API URL from the sibling
prefect-server
service name --- ## How this fits together At runtime, the MCP deployment will connect to:
Copy code
text
http://<release-name>-prefect-server:4200/api
So if your Helm release is
prefect
, the MCP server will call:
Copy code
text
<http://prefect-prefect-server:4200/api>
That is the normal dependency service naming pattern in Helm. --- ## Optional next file: ingress You said “template file if needed”; if you want, I can also add a full
templates/ingress.yaml
for the MCP server. I left it out above because many self-hosted installs keep this internal-only behind
ClusterIP
. --- ## What I’d recommend changing before using this in prod 1. Pin your MCP image explicitly and build it from the Prefect 3.7.2 base image. 2. *Use HTTP probes only if your image exposes a stable health endpoint*; otherwise TCP probes are safer. 3. If your self-hosted API has auth in front of it, set: *
prefect-mcp-server.prefectApi.apiKeySecretName
, or *
prefect-mcp-server.prefectApi.apiAuthStringSecretName
4. Verify the actual
prefect-server
service name in your rendered manifests with:
helm template ...
--- If you want, I can do either of these next: * Option A: give you a single combined chart tree including an
ingress.yaml
* Option B: rewrite this specifically against the current upstream
prefect-server
chart values structure
if you paste your existing
values.yaml
or tell me the exact chart version you’re on Useful links: * prefect-helm * prefect-mcp-server
r
@Marvin Ok, that's horribly complicated. I won't proceed.
m
thought for 12.1 seconds
Fair. This is the simple version: There is no official
prefect-mcp-server
subchart in
prefect-helm
right now
, so making it “fit cleanly” as a reusable subchart means inventing chart structure around a separate app/image. That’s why it got bulky. The simplest practical path is usually one of these: * Don’t helm it yet — run
prefect-mcp-server
separately until there’s an official chart/pattern. * Use one plain Deployment + Service manifest instead of a subchart. * Build one custom image from
prefecthq/prefect:3.7.2
with
prefect-mcp
installed
, then deploy that with minimal YAML. If you want, I can give you a very small version next — just: *
deployment.yaml
*
service.yaml
* a tiny
values.yaml
No subchart ceremony, no helper maze, just enough to point at your self-hosted Prefect API. If that’s what you want, I can make it ~60–80 lines total.