Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# Copy to .env for local development.
MKDOCS_MCP_BASE_URL=https://docs.example.com
# Override only if the published index is not at BASE_URL/search/search_index.json.
# MKDOCS_MCP_SEARCH_INDEX_URL=https://docs.example.com/custom/search_index.json
# For multiple sites, use JSON. This replaces BASE_URL and SEARCH_INDEX_URL.
# MKDOCS_MCP_SITES=[{"name":"atlas","base_url":"https://atlas.example.com"},{"name":"team","base_url":"https://team.example.com"}]
MKDOCS_MCP_SITES=[{"name":"docs","base_url":"https://docs.example.com"}]
# Add more objects to the list for more sites. A site may include search_index_url.

MKDOCS_MCP_REINDEX_INTERVAL_MINUTES=30
MKDOCS_MCP_LOG_JSON=false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Check Helm chart
run: |
helm lint charts/mkdocs-mcp
helm template mkdocs-mcp charts/mkdocs-mcp -f charts/mkdocs-mcp/values-production.yaml > /dev/null
helm template mkdocs-mcp charts/mkdocs-mcp > /dev/null
- name: Check Docker build on pull requests
if: github.event_name == 'pull_request'
run: docker build --tag mkdocs-mcp:ci .
Expand Down
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,22 @@ Requires Python 3.14 and `uv`.
```bash
uv sync --locked --python 3.14
source .venv/bin/activate
export MKDOCS_MCP_BASE_URL=https://docs.example.com
export MKDOCS_MCP_SITES='[{"name":"docs","base_url":"https://docs.example.com"}]'
mkdocs-mcp
```

Replace the example URL with the public root of your MkDocs site. By default,
the server fetches `<base-url>/search/search_index.json` on startup and serves
MCP at `http://localhost:8000/mcp`. Set `MKDOCS_MCP_SEARCH_INDEX_URL` if your
site publishes its search index elsewhere. The index must be reachable when
the server starts.
Replace the example URL with the public root of your MkDocs site. The server
fetches `<base_url>/search/search_index.json` on startup and serves MCP at
`http://localhost:8000/mcp`. If the index is elsewhere, add `search_index_url`
to that site's object. The index must be reachable when the server starts.

Opening `/` in a browser shows the server status and MCP connection URL.
Opening `/mcp` in a browser shows the same page; MCP clients continue to use
`/mcp` for protocol requests.

To search several MkDocs sites through one endpoint, set `MKDOCS_MCP_SITES` to
a JSON list of site names and published URLs. Search results identify their
site and keep links to the original pages:
To search several sites through the same endpoint, add more entries to
`MKDOCS_MCP_SITES`. Search results identify their site and link to the original
pages:

```bash
export MKDOCS_MCP_SITES='[{"name":"atlas","base_url":"https://atlas.example.com"},{"name":"team","base_url":"https://team.example.com"}]'
Expand Down
93 changes: 29 additions & 64 deletions charts/mkdocs-mcp/README.md
Original file line number Diff line number Diff line change
@@ -1,84 +1,49 @@
# Deploying the MkDocs MCP server on OKD / OpenShift
# Deploying MkDocs MCP on OKD / OpenShift

A generic, configurable MCP server for any MkDocs documentation site. All
site-specific values (URLs and server name) are supplied via Helm values
/ env — nothing is hardcoded.
This chart uses one [`values.yaml`](values.yaml) for every deployment. Edit
`config.sites` to list one or more public MkDocs sites and set `route.host` to
your MCP hostname. Each site normally serves its index at
`<base_url>/search/search_index.json`; add `search_index_url` to an entry if its
index lives elsewhere. Replace the example addresses before deployment.

## Prerequisites
- An OKD/OpenShift project (namespace).
- The image pushed to your registry (see root `Dockerfile` / `.github/workflows/release.yml`,
which publishes to `ghcr.io/<owner>/<repo>`).
- Prometheus Operator if you want the `ServiceMonitor`.
The pod needs DNS and HTTPS access to every configured site. MCP clients do not
need a token. A Prometheus Operator is needed only if `serviceMonitor.enabled`
is true.

## 1. Configure the documentation source
Copy `values-production.yaml` to `values-production.local.yaml` and set
`config.baseUrl` and `route.host` there. Set `config.searchIndexUrl` only if
the index is not at `<baseUrl>/search/search_index.json`. The local
file is ignored by Git. The checked-in values are examples and must not
contain private deployment addresses. MCP clients do not need a token. The
pod must be able to fetch the published search index over HTTPS.
## Install or upgrade

For one MCP endpoint serving several MkDocs sites, set `config.sites` to a
list of `{name, base_url}` entries as shown in
[configuration](../../docs/CONFIGURATION.md). Each site's search index is read
from its own URL; `config.baseUrl` is used only when the list is empty. The
pod needs DNS and HTTPS egress to every configured site.

## 2. Install / upgrade
The chart in this checkout defaults to the moving `main` image. Pin
`image.tag=sha-<commit>` for a repeatable deployment, as shown below. A
versioned chart attached to a GitHub release defaults to its matching image tag.
Pin `image.tag` to a built image version. For a checkout using the GitHub
workflow's commit image:

```bash
helm upgrade --install mkdocs-mcp charts/mkdocs-mcp \
-n docs-mcp-staging --create-namespace \
-f charts/mkdocs-mcp/values-staging.yaml \
--set image.tag=sha-$(git rev-parse HEAD) \
--set config.baseUrl=https://docs.example.com

helm upgrade --install mkdocs-mcp charts/mkdocs-mcp \
-n docs-mcp --create-namespace \
-f charts/mkdocs-mcp/values-production.local.yaml \
--set image.tag=sha-$(git rev-parse HEAD)
```

## 3. Verify
The chart defaults to one replica, with CPU request/limit of 250m/500m and
memory request/limit of 512Mi/1Gi. HPA, PDB, and NetworkPolicy templates are
available but disabled by default in `values.yaml`. Readiness gates traffic
until a documentation index has loaded.
The container uses a non-root, read-only filesystem with writable `/tmp`.
OpenShift assigns the UID through its SCC.

## Verify

```bash
oc -n docs-mcp rollout status deploy/mkdocs-mcp
oc -n docs-mcp get route mkdocs-mcp
curl -fsS https://<route-host>/health
curl -fsS https://<route-host>/ready
```

Open `https://<route-host>/` in a browser for the connection page and current
index status. A browser request to `/mcp` shows that page too; MCP clients use
`/mcp` as before.

## 4. Connect a client
```json
{ "mcpServers": { "mkdocs-docs": {
"type": "http",
"url": "https://<route-host>/mcp" } } }
```

## Operational notes
- **Indexing**: each replica reads the published search index on startup and
checks `ETag` or `Last-Modified` every `config.reindexIntervalMinutes`.
No Git clone, Secret, or persistent volume is required.
- **Scaling/HA**: `replicaCount>=2`, pod anti-affinity, `PodDisruptionBudget`,
optional HPA. Readiness gates traffic until the index is loaded.
- **Security**: non-root, read-only rootfs (writable `/tmp` only),
dropped caps, `NetworkPolicy` limiting egress to DNS + HTTPS. OpenShift
assigns the UID via SCC — do not set `runAsUser`.
- **Access**: anyone who can reach `/mcp` can use its read-only documentation
tools. The server reads only the public MkDocs search index.
Open `https://<route-host>/` for the UI. MCP clients connect to
`https://<route-host>/mcp`.

## Troubleshooting
| Symptom | Check |
|---|---|
| Pod not ready | `oc logs` — search index URL or HTTPS egress failing? |
| Empty results | Check the published `search_index.json` and `get_statistics` |
| Wrong links | Check `config.baseUrl`, or each `config.sites[].base_url` in multi-site mode |
The server holds the published search indexes in memory and periodically checks
for changes using HTTP `ETag` or `Last-Modified` when available. No Git clone or
persistent volume is needed. Memory use grows with the combined index size of
the sites; monitor it before adding many large sites.

See [logs and metrics](../../docs/OBSERVABILITY.md) for the available metrics
and example dashboard and alert expressions.
See [configuration](../../docs/CONFIGURATION.md) and
[observability](../../docs/OBSERVABILITY.md) for more detail.
4 changes: 1 addition & 3 deletions charts/mkdocs-mcp/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ metadata:
data:
MKDOCS_MCP_HOST: {{ .Values.config.host | quote }}
MKDOCS_MCP_PORT: {{ .Values.config.port | quote }}
MKDOCS_MCP_SEARCH_INDEX_URL: {{ .Values.config.searchIndexUrl | quote }}
MKDOCS_MCP_SITES: {{ .Values.config.sites | toJson | quote }}
MKDOCS_MCP_BASE_URL: {{ .Values.config.baseUrl | quote }}
MKDOCS_MCP_SITES: {{ required "config.sites must contain at least one MkDocs site" .Values.config.sites | toJson | quote }}
MKDOCS_MCP_REINDEX_INTERVAL_MINUTES: {{ .Values.config.reindexIntervalMinutes | quote }}
MKDOCS_MCP_RATE_LIMIT_ENABLED: {{ .Values.config.rateLimitEnabled | quote }}
MKDOCS_MCP_RATE_LIMIT_PER_SECOND: {{ .Values.config.rateLimitPerSecond | quote }}
Expand Down
9 changes: 0 additions & 9 deletions charts/mkdocs-mcp/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,6 @@ spec:
imagePullSecrets: {{- toYaml . | nindent 8 }}
{{- end }}
securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- with .Values.affinity }}
affinity: {{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations: {{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: mkdocs-mcp
image: {{ include "mkdocs-mcp.image" . }}
Expand Down
22 changes: 0 additions & 22 deletions charts/mkdocs-mcp/values-production.yaml

This file was deleted.

12 changes: 0 additions & 12 deletions charts/mkdocs-mcp/values-staging.yaml

This file was deleted.

45 changes: 12 additions & 33 deletions charts/mkdocs-mcp/values.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Default values for mkdocs-mcp.
replicaCount: 2
replicaCount: 1

image:
repository: ghcr.io/ehsanmsb/mkdocs-mcp
Expand All @@ -15,9 +15,9 @@ config:
serverName: MkDocs Docs # surfaced to MCP clients
host: 0.0.0.0
port: 8000
searchIndexUrl: "" # defaults to baseUrl/search/search_index.json
sites: [] # [{name: team, base_url: https://docs.example.com}]
baseUrl: https://docs.example.com
sites:
- name: docs
base_url: https://docs.example.com
reindexIntervalMinutes: 30
rateLimitEnabled: true
rateLimitPerSecond: 5
Expand All @@ -40,38 +40,30 @@ route:
termination: edge
insecureEdgeTerminationPolicy: Redirect

ingress:
enabled: false
className: ""
host: ""
annotations: {}
tls: []

resources:
requests:
cpu: 100m
memory: 192Mi
limits:
cpu: "1"
cpu: 250m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi

autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 5
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 75

podDisruptionBudget:
enabled: true
enabled: false
minAvailable: 1

serviceMonitor:
enabled: true
interval: 30s

networkPolicy:
enabled: true
# CIDRs / selectors allowed to reach the MCP port (e.g. the ingress controller).
enabled: false
ingressFrom: []

# OpenShift-friendly: do NOT pin runAsUser; let the SCC assign the UID.
Expand All @@ -84,16 +76,3 @@ securityContext:
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]

nodeSelector: {}
tolerations: []
# Spread replicas across nodes for HA.
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app.kubernetes.io/name: mkdocs-mcp
Loading
Loading