Container-Image fuer den MCP-Leseserver: Build, Registry-Push, und was am Korpus-Sync noch offen ist #37

Open
opened 2026-09-02 05:25:29 +00:00 by torben · 1 comment
Owner

Der Server aus #19 läuft (2.4.0), auf stdio und streamable-http, beide gegen den echten
Korpus gegengeprüft. Was fehlt, ist der Weg vom Repo zu einem laufenden Ding: ein Image, ein
Build, ein Push in die Registry.

Dieses Issue fasst zusammen, was schon feststeht. Die Details sind bewusst noch offen — sie
brauchen Entscheidungen des Betreibers, und die Liste unten sagt, welche. Wer das aufgreift,
soll nicht bei null anfangen, aber auch nicht glauben, hier stünde schon ein Plan.

Seit 2026-09-04 trägt dieses Issue auch den Abschluss des Master-Issues #36. Dessen letztes
offenes Kriterium — ein Konsument führt über die Traefik-Middleware nachweislich search gegen
den Server aus — hing vollständig an einem Deployment und damit an diesem Image. #36 ist als
Entscheidungsprotokoll geschlossen; der Nachweis steht unten als eigenes Akzeptanzkriterium.
Damit ist dies das letzte offene Issue auf dem Weg zum MCP-Leseserver. Die Entscheidungen,
die dort getroffen wurden und hier nicht neu aufgemacht werden (Python statt Go, Traefik statt
eigener Auth, Polling statt Webhook, Direktimport statt Subprozess, Deployment gehört nicht in
dieses Repo), stehen im geschlossenen Body von #36 samt gemessener Grundlage.

Abgrenzung, die bleibt: Cluster, Ingress-Hosts, Secret-Store und die FluxCD-Quelle gehören
nicht hierher. Dies ist ein öffentlicher Tracker, das ist private Infrastruktur. Was hierher
gehört, ist alles, was eine Eigenschaft der Software ist: das Dockerfile, der Workflow, was
das Image an Konfiguration erwartet.


Was schon feststeht

Was ausgeliefert wird. tools/chemenu/mcp/, gestartet als
python -m chemenu.mcp --transport streamable-http --host 0.0.0.0 --port 8000. Endpunkt
/mcp. Lesend, ohne Schreibpfad — strukturell, weil nichts unter chemenu/commands/ importiert
wird.

Konfiguration, die das Image kennen muss (alles heute schon implementiert):

Variable Zweck Ohne sie
CHEMENU_ROOT Welcher Korpus bedient wird der Checkout, in dem das Paket liegt
WIKI_TRACE=0 oder WIKI_TRACE_DIR außerhalb des Korpus Telemetrie aus dem bedienten Baum halten der Server verweigert den Start — bewusst, nicht als Bug

Abhängigkeiten. tools/requirements.txt plus tools/requirements-mcp.txt (letzteres zieht
mcp, und damit pydantic/starlette/uvicorn/cryptography). Dazu ripgrep im Image — das ist
die Falle, die ein reines pip install-Dockerfile übersieht: search shellt nach rg aus und
hat absichtlich keinen Python-Fallback.

Authentifizierung ist gelöst und liegt woanders:
https://gitea.nehmer.net/torben/gitea-mcp-forward-auth — Traefik-ForwardAuth, Bearer-Token
gegen SHA-256-Hashes. Der Server authentifiziert selbst nichts. Rate Limiting gehört an
dieselbe Stelle, nicht in den Iteration Budget Gate (Begründung in #19).

Menschendoku steht: INSTALL-MCP.md, Betriebsablauf in
instructions/mcp-read-server.md.


Startpunkte, konkret

Zwei Vorlagen im eigenen Bestand, und sie taugen für verschiedene Hälften:

torben/gitea-mcp-forward-auth, .gitea/workflows/release.yaml — die nähere Vorlage.
Sie pusht dorthin, wo wir auch hin wollen:

  • runs-on: container-builder, Job-Image debian:trixie-slim
  • CI-Deps per apt: git nodejs curl docker-cli docker-buildx unzip ca-certificates iproute2 gawk
  • Secrets über 1password/load-secrets-action@v2 (OP_SERVICE_ACCOUNT_TOKEN), Registry-User
    und PAT aus op://CI-CD/gitea-package-token/...
  • Remote-BuildKit: HOST_IP=$(ip route | awk '/default/ { print $3 }'), dann
    docker buildx create --driver remote tcp://$HOST_IP:1234 --use --bootstrap
  • Trigger auf Tags v*.*.*; stabile Tags vX.Y.Z pushen zusätzlich :latest, Pre-Releases
    stattdessen :prerelease-tag
  • Registry gitea.nehmer.net/torben, Plattform bislang nur linux/amd64

torben/gitea-mcp — Dockerfile-Muster, aber nicht der Push. Dessen Workflows pushen nach
DockerHub (DOCKER_ORG: gitea), weil das Repo ein Fork des Upstream ist; als Registry-Vorlage
also unbrauchbar. Wofür es taugt, ist die Image-Form: Multi-Stage, --platform=$BUILDPLATFORM,
--mount=type=cache für den Dependency-Download, ARG VERSION ins Binary, non-root Runtime auf
distroless, OCI-Labels (org.opencontainers.image.version/.source). Dazu ein Muster, das
gitea-mcp hat und forward-auth nicht: ein nightly-Workflow auf main mit
git describe --tags --always als Version.

Ebenfalls im forward-auth-Repo und übernehmenswert: scripts/test-registry-image.sh — zieht das
veröffentlichte Image, startet es und prüft es end-to-end, mit ephemerem Login (temporäres
DOCKER_CONFIG bzw. REGISTRY_AUTH_FILE, nichts in ~/.docker/config.json).

Stand im Repo, geprüft 2026-09-04 gegen 4.5.0: es gibt weiterhin kein Dockerfile und keinen
Image-Workflow. .gitea/workflows/ enthält ci.yml, nightly.yml, release.yml.


Was zu klären ist

Das ist die eigentliche Arbeit dieses Issues — jede Zeile ist eine Entscheidung, keine Aufgabe.

  1. Was steckt im Image, Stack oder Stack+Korpus? Der Server liest einen Arbeitsbaum. Zwei
    Formen: (a) Image trägt nur den Stack, der Korpus kommt als Volume und wird von einem
    Sidecar/CronJob gesynct; (b) Image trägt einen Korpus-Stand und wird bei jeder Änderung neu
    gebaut. (a) passt zum Polling-Design aus #19 und hält den Korpus aus der Registry heraus —
    was bei einer privaten Instanz der Punkt ist. (b) macht das Image reproduzierbar und den
    Sync überflüssig. Erst entscheiden, dann Dockerfile.

  2. Wer führt git fetch && git reset --hard aus? Sidecar, CronJob, oder ein Loop im
    Entrypoint. Braucht ein Read-Credential für das Korpus-Repo — für die öffentliche Instanz
    keins, für die private schon. Hängt an 1.

  3. Basis-Image. distroless wie die beiden Vorlagen geht bei Go, hier aber nicht ohne
    Weiteres: es braucht einen Python-Interpreter und das rg-Binary. Realistisch
    python:3.13-slim plus ripgrep per apt, oder ein Multi-Stage, das rg statisch
    hereinkopiert. Non-root-Runtime sollte in beiden Fällen bleiben.

  4. Wie kommt die Version ins Image? Der Stack hat VERSION und wikitool version bump;
    CI taggt bereits bei VERSION-Bewegung (.gitea/workflows/release.yml). Naheliegend ist,
    den bestehenden Release-Trigger mitzubenutzen, statt einen zweiten Tag-Begriff einzuführen.
    Zu prüfen: ob der Image-Build an denselben Tag hängt oder an einen eigenen.

  5. linux/arm64 mitbauen? forward-auth baut nur amd64, gitea-mcp beides. Kostet Buildzeit;
    nur entscheiden, wenn ein Ziel es braucht.

  6. Healthcheck. Der Server hat heute keinen/healthz gibt es beim Auth-Proxy, nicht
    hier. Für k8s-Probes braucht es entweder einen Endpunkt (dann: neuer Code im Server, ohne
    Auth erreichbar, wie beim Proxy) oder eine Probe über den MCP-Endpunkt. Das ist die einzige
    Zeile hier, die auf eine Änderung in diesem Repo hinausläuft.

  7. Registry-Pfad und Image-Name. Analog gitea.nehmer.net/torben/<name>. Nur festlegen.

  8. OCI-Labels. forward-auth hat heute keine, das Testskript prüft sie optional. Beim
    Neubauen gleich richtig setzen (image.source, image.version, image.title).

  9. Wie wird der Golden-Test im Image-Kontext gefahren, wenn überhaupt? Die Suite läuft in
    CI gegen den Checkout. Ob ein Smoke-Test gegen das gebaute Image dazukommt (Vorbild:
    test-registry-image.sh), ist eine eigene Entscheidung — die Analogie wäre: Image ziehen,
    starten, list_tools und ein status gegen den mitgelieferten Demo-Korpus.


Akzeptanzkriterien

  • Entscheidung zu Punkt 1 (Korpus im Image oder als Volume) getroffen und im Issue vermerkt
  • Dockerfile im Repo, non-root, mit ripgrep, beide requirements-Dateien installiert
  • Gebautes Image startet mit --transport streamable-http --host 0.0.0.0 und beantwortet
    list_tools sowie ein status
  • Der Trace-Guard ist im Image erfüllt (WIKI_TRACE=0 oder WIKI_TRACE_DIR außerhalb) —
    sonst startet der Container gar nicht, und das soll niemanden überraschen
  • Workflow pusht nach gitea.nehmer.net/torben/<name>, Tag-Schema festgelegt und
    dokumentiert
  • Sync-Mechanismus aus Punkt 2 gebaut oder ausdrücklich als Deployment-Sache abgegrenzt
  • Entscheidung zu Punkt 6 (Healthcheck) — wenn er kommt, mit Test und Version-Bump
  • INSTALL-MCP.md um den Container-Weg ergänzt; der Verweis auf „ein Container-Image ist
    als eigenes Vorhaben erfasst" dort aufgelöst
  • Übernommen aus #36: Ein Konsument führt über die Traefik-Middleware nachweislich
    search gegen den laufenden Server aus. Das ist der Abschlussnachweis für den gesamten
    Weg zum MCP-Leseserver, nicht nur für das Image — er kann erst erbracht werden, wenn ein
    Deployment steht, und deshalb steht er hier.

Hängt ab von: #19 (geschlossen, 2.4.0). Schließt ab: #36 (geschlossen 2026-09-04, letztes
Kriterium hierher übernommen). Verwandt: #32 — eine Ingest-Queue würde einen Schreibpfad und
damit ein anderes Container-Profil brauchen; nicht vorwegnehmen.

Der Server aus #19 läuft (2.4.0), auf `stdio` und `streamable-http`, beide gegen den echten Korpus gegengeprüft. Was fehlt, ist der Weg vom Repo zu einem laufenden Ding: ein Image, ein Build, ein Push in die Registry. **Dieses Issue fasst zusammen, was schon feststeht.** Die Details sind bewusst noch offen — sie brauchen Entscheidungen des Betreibers, und die Liste unten sagt, welche. Wer das aufgreift, soll nicht bei null anfangen, aber auch nicht glauben, hier stünde schon ein Plan. **Seit 2026-09-04 trägt dieses Issue auch den Abschluss des Master-Issues #36.** Dessen letztes offenes Kriterium — ein Konsument führt über die Traefik-Middleware nachweislich `search` gegen den Server aus — hing vollständig an einem Deployment und damit an diesem Image. #36 ist als Entscheidungsprotokoll geschlossen; der Nachweis steht unten als eigenes Akzeptanzkriterium. **Damit ist dies das letzte offene Issue auf dem Weg zum MCP-Leseserver.** Die Entscheidungen, die dort getroffen wurden und hier nicht neu aufgemacht werden (Python statt Go, Traefik statt eigener Auth, Polling statt Webhook, Direktimport statt Subprozess, Deployment gehört nicht in dieses Repo), stehen im geschlossenen Body von #36 samt gemessener Grundlage. **Abgrenzung, die bleibt:** Cluster, Ingress-Hosts, Secret-Store und die FluxCD-Quelle gehören **nicht** hierher. Dies ist ein öffentlicher Tracker, das ist private Infrastruktur. Was hierher gehört, ist alles, was eine Eigenschaft der *Software* ist: das Dockerfile, der Workflow, was das Image an Konfiguration erwartet. --- ## Was schon feststeht **Was ausgeliefert wird.** `tools/chemenu/mcp/`, gestartet als `python -m chemenu.mcp --transport streamable-http --host 0.0.0.0 --port 8000`. Endpunkt `/mcp`. Lesend, ohne Schreibpfad — strukturell, weil nichts unter `chemenu/commands/` importiert wird. **Konfiguration, die das Image kennen muss** (alles heute schon implementiert): | Variable | Zweck | Ohne sie | |---|---|---| | `CHEMENU_ROOT` | Welcher Korpus bedient wird | der Checkout, in dem das Paket liegt | | `WIKI_TRACE=0` **oder** `WIKI_TRACE_DIR` außerhalb des Korpus | Telemetrie aus dem bedienten Baum halten | **der Server verweigert den Start** — bewusst, nicht als Bug | **Abhängigkeiten.** `tools/requirements.txt` plus `tools/requirements-mcp.txt` (letzteres zieht `mcp`, und damit pydantic/starlette/uvicorn/cryptography). Dazu **`ripgrep` im Image** — das ist die Falle, die ein reines `pip install`-Dockerfile übersieht: `search` shellt nach `rg` aus und hat absichtlich keinen Python-Fallback. **Authentifizierung ist gelöst und liegt woanders:** <https://gitea.nehmer.net/torben/gitea-mcp-forward-auth> — Traefik-ForwardAuth, Bearer-Token gegen SHA-256-Hashes. Der Server authentifiziert selbst nichts. Rate Limiting gehört an dieselbe Stelle, **nicht** in den Iteration Budget Gate (Begründung in #19). **Menschendoku steht:** [`INSTALL-MCP.md`](../src/branch/main/INSTALL-MCP.md), Betriebsablauf in `instructions/mcp-read-server.md`. --- ## Startpunkte, konkret Zwei Vorlagen im eigenen Bestand, und sie taugen für **verschiedene Hälften**: **`torben/gitea-mcp-forward-auth`, `.gitea/workflows/release.yaml` — die nähere Vorlage.** Sie pusht dorthin, wo wir auch hin wollen: - `runs-on: container-builder`, Job-Image `debian:trixie-slim` - CI-Deps per apt: `git nodejs curl docker-cli docker-buildx unzip ca-certificates iproute2 gawk` - Secrets über `1password/load-secrets-action@v2` (`OP_SERVICE_ACCOUNT_TOKEN`), Registry-User und PAT aus `op://CI-CD/gitea-package-token/...` - Remote-BuildKit: `HOST_IP=$(ip route | awk '/default/ { print $3 }')`, dann `docker buildx create --driver remote tcp://$HOST_IP:1234 --use --bootstrap` - Trigger auf Tags `v*.*.*`; stabile Tags `vX.Y.Z` pushen zusätzlich `:latest`, Pre-Releases stattdessen `:prerelease-tag` - Registry `gitea.nehmer.net/torben`, Plattform bislang nur `linux/amd64` **`torben/gitea-mcp` — Dockerfile-Muster, aber nicht der Push.** Dessen Workflows pushen nach **DockerHub** (`DOCKER_ORG: gitea`), weil das Repo ein Fork des Upstream ist; als Registry-Vorlage also unbrauchbar. Wofür es taugt, ist die Image-Form: Multi-Stage, `--platform=$BUILDPLATFORM`, `--mount=type=cache` für den Dependency-Download, `ARG VERSION` ins Binary, non-root Runtime auf `distroless`, OCI-Labels (`org.opencontainers.image.version`/`.source`). Dazu ein Muster, das gitea-mcp hat und forward-auth nicht: ein **nightly**-Workflow auf `main` mit `git describe --tags --always` als Version. Ebenfalls im forward-auth-Repo und übernehmenswert: `scripts/test-registry-image.sh` — zieht das **veröffentlichte** Image, startet es und prüft es end-to-end, mit ephemerem Login (temporäres `DOCKER_CONFIG` bzw. `REGISTRY_AUTH_FILE`, nichts in `~/.docker/config.json`). **Stand im Repo, geprüft 2026-09-04 gegen 4.5.0:** es gibt weiterhin kein `Dockerfile` und keinen Image-Workflow. `.gitea/workflows/` enthält `ci.yml`, `nightly.yml`, `release.yml`. --- ## Was zu klären ist Das ist die eigentliche Arbeit dieses Issues — jede Zeile ist eine Entscheidung, keine Aufgabe. 1. **Was steckt im Image, Stack oder Stack+Korpus?** Der Server liest einen Arbeitsbaum. Zwei Formen: (a) Image trägt nur den Stack, der Korpus kommt als Volume und wird von einem Sidecar/CronJob gesynct; (b) Image trägt einen Korpus-Stand und wird bei jeder Änderung neu gebaut. (a) passt zum Polling-Design aus #19 und hält den Korpus aus der Registry heraus — was bei einer **privaten** Instanz der Punkt ist. (b) macht das Image reproduzierbar und den Sync überflüssig. Erst entscheiden, dann Dockerfile. 2. **Wer führt `git fetch && git reset --hard` aus?** Sidecar, CronJob, oder ein Loop im Entrypoint. Braucht ein Read-Credential für das Korpus-Repo — für die öffentliche Instanz keins, für die private schon. Hängt an 1. 3. **Basis-Image.** `distroless` wie die beiden Vorlagen geht bei Go, hier aber nicht ohne Weiteres: es braucht einen Python-Interpreter *und* das `rg`-Binary. Realistisch `python:3.13-slim` plus `ripgrep` per apt, oder ein Multi-Stage, das `rg` statisch hereinkopiert. Non-root-Runtime sollte in beiden Fällen bleiben. 4. **Wie kommt die Version ins Image?** Der Stack hat `VERSION` und `wikitool version bump`; CI taggt bereits bei `VERSION`-Bewegung (`.gitea/workflows/release.yml`). Naheliegend ist, den bestehenden Release-Trigger mitzubenutzen, statt einen zweiten Tag-Begriff einzuführen. Zu prüfen: ob der Image-Build an denselben Tag hängt oder an einen eigenen. 5. **`linux/arm64` mitbauen?** forward-auth baut nur amd64, gitea-mcp beides. Kostet Buildzeit; nur entscheiden, wenn ein Ziel es braucht. 6. **Healthcheck.** Der Server hat heute **keinen** — `/healthz` gibt es beim Auth-Proxy, nicht hier. Für k8s-Probes braucht es entweder einen Endpunkt (dann: neuer Code im Server, ohne Auth erreichbar, wie beim Proxy) oder eine Probe über den MCP-Endpunkt. Das ist die einzige Zeile hier, die auf eine Änderung **in diesem Repo** hinausläuft. 7. **Registry-Pfad und Image-Name.** Analog `gitea.nehmer.net/torben/<name>`. Nur festlegen. 8. **OCI-Labels.** forward-auth hat heute keine, das Testskript prüft sie optional. Beim Neubauen gleich richtig setzen (`image.source`, `image.version`, `image.title`). 9. **Wie wird der Golden-Test im Image-Kontext gefahren, wenn überhaupt?** Die Suite läuft in CI gegen den Checkout. Ob ein Smoke-Test gegen das *gebaute* Image dazukommt (Vorbild: `test-registry-image.sh`), ist eine eigene Entscheidung — die Analogie wäre: Image ziehen, starten, `list_tools` und ein `status` gegen den mitgelieferten Demo-Korpus. --- ## Akzeptanzkriterien - [ ] Entscheidung zu Punkt 1 (Korpus im Image oder als Volume) getroffen und im Issue vermerkt - [ ] Dockerfile im Repo, non-root, mit `ripgrep`, beide requirements-Dateien installiert - [ ] Gebautes Image startet mit `--transport streamable-http --host 0.0.0.0` und beantwortet `list_tools` sowie ein `status` - [ ] Der Trace-Guard ist im Image erfüllt (`WIKI_TRACE=0` oder `WIKI_TRACE_DIR` außerhalb) — sonst startet der Container gar nicht, und das soll niemanden überraschen - [ ] Workflow pusht nach `gitea.nehmer.net/torben/<name>`, Tag-Schema festgelegt und dokumentiert - [ ] Sync-Mechanismus aus Punkt 2 gebaut oder ausdrücklich als Deployment-Sache abgegrenzt - [ ] Entscheidung zu Punkt 6 (Healthcheck) — wenn er kommt, mit Test und Version-Bump - [ ] `INSTALL-MCP.md` um den Container-Weg ergänzt; der Verweis auf „ein Container-Image ist als eigenes Vorhaben erfasst" dort aufgelöst - [ ] **Übernommen aus #36:** Ein Konsument führt über die Traefik-Middleware nachweislich `search` gegen den laufenden Server aus. Das ist der Abschlussnachweis für den gesamten Weg zum MCP-Leseserver, nicht nur für das Image — er kann erst erbracht werden, wenn ein Deployment steht, und deshalb steht er hier. **Hängt ab von:** #19 (geschlossen, 2.4.0). **Schließt ab:** #36 (geschlossen 2026-09-04, letztes Kriterium hierher übernommen). **Verwandt:** #32 — eine Ingest-Queue würde einen Schreibpfad und damit ein anderes Container-Profil brauchen; nicht vorwegnehmen.
torben added the prio/plannedsize/M labels 2026-09-02 05:25:29 +00:00
torben added the area/distributionkind/decision labels 2026-09-02 21:24:42 +00:00
Author
Owner

Changelog: Dieses Issue trägt seit der Triage-Sitzung 2026-09-04 den Abschluss von #36, das dabei geschlossen wurde.

  • Neues, letztes Akzeptanzkriterium: „Ein Konsument führt über die Traefik-Middleware nachweislich search gegen den laufenden Server aus." Es stand als einziger offener Punkt in #36 und hing dort vollständig an einem Deployment und damit an diesem Image — es gehört zu den Entscheidungen, die hier ohnehin getroffen werden, nicht in ein Master-Issue, das nichts mehr steuert.
  • Kopfabsatz ergänzt: dies ist das letzte offene Issue auf dem Weg zum MCP-Leseserver. Die getroffenen Entscheidungen (Python statt Go, Traefik statt eigener Auth, Polling statt Webhook, Direktimport statt Subprozess, Deployment gehört nicht in dieses Repo) stehen im geschlossenen Body von #36 und werden hier nicht neu aufgemacht.
  • Repo-Stand gegen 4.5.0 nachgeprüft und im Text festgehalten: weiterhin kein Dockerfile, kein Image-Workflow, .gitea/workflows/ hat ci.yml, nightly.yml, release.yml.
  • Abhängigkeitszeile um „Schließt ab: #36" ergänzt.

Labels unverändert: area/distribution, kind/decision, prio/planned, size/M. Die neun offenen Punkte sind Betreiberentscheidungen, nicht Umsetzungszeit — kind/decision bleibt richtig.

**Changelog:** Dieses Issue trägt seit der Triage-Sitzung 2026-09-04 den Abschluss von **#36**, das dabei geschlossen wurde. - Neues, letztes Akzeptanzkriterium: „Ein Konsument führt über die Traefik-Middleware nachweislich `search` gegen den laufenden Server aus." Es stand als einziger offener Punkt in #36 und hing dort vollständig an einem Deployment und damit an diesem Image — es gehört zu den Entscheidungen, die hier ohnehin getroffen werden, nicht in ein Master-Issue, das nichts mehr steuert. - Kopfabsatz ergänzt: **dies ist das letzte offene Issue auf dem Weg zum MCP-Leseserver.** Die getroffenen Entscheidungen (Python statt Go, Traefik statt eigener Auth, Polling statt Webhook, Direktimport statt Subprozess, Deployment gehört nicht in dieses Repo) stehen im geschlossenen Body von #36 und werden hier nicht neu aufgemacht. - Repo-Stand gegen 4.5.0 nachgeprüft und im Text festgehalten: weiterhin kein `Dockerfile`, kein Image-Workflow, `.gitea/workflows/` hat `ci.yml`, `nightly.yml`, `release.yml`. - Abhängigkeitszeile um „**Schließt ab:** #36" ergänzt. Labels unverändert: `area/distribution`, `kind/decision`, `prio/planned`, `size/M`. Die neun offenen Punkte sind Betreiberentscheidungen, nicht Umsetzungszeit — `kind/decision` bleibt richtig.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#37