Russell Brooks
05/28/2026, 11:20 AMMarvin
05/28/2026, 11:20 AMMarvin
05/28/2026, 11:23 AMprefect-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:
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.
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: {}Marvin
05/28/2026, 11:23 AM---
*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
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.
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" -}}Marvin
05/28/2026, 11:23 AM---
*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
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 }}Marvin
05/28/2026, 11:23 AM---
## Important practical note about the image
That deployment assumes your image contains the source at:
```text
/app/src/prefect_mcp_server/server.pyMarvin
05/28/2026, 11:23 AMcommand to call the installed entrypoint or a module runner.
A more image-agnostic command is usually better, for example:
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:
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:
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:
text
http://<release-name>-prefect-server:4200/api
So if your Helm release is prefect, the MCP server will call:
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-serverRussell Brooks
05/28/2026, 12:13 PMMarvin
05/28/2026, 12:13 PMMarvin
05/28/2026, 12:14 PMprefect-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.