Ein MCP-Server ist beschlossen, nicht mehr bedingt: es wird mehrere Konsumenten geben, der
Perplexity Space war nur der erste. Zwei Grundsatzfragen sind am 2026-09-01 entschieden und
stehen hier nicht mehr zur Debatte (Herleitung im Kommentarverlauf):
Plattform: Python. Kein Go, auch nicht als Option.
Authentifizierung: Traefik-Middleware. Nicht sauber authentifizierte Zugriffe erreichen den
Python-Prozess nicht.
Befund
wikitool ist ein lokales CLI ohne Netzwerkschnittstelle. Der gitea-mcp-Connector bildet nur die
Gitea-REST-API ab, keinen Ausführungs-Endpunkt. Ohne eigene Ausführungsschicht kann ein externer
Konsument nur über einzelne get_file_contents-Aufrufe lesen — langsamer und ungenauer als wikitool search.
Die tragfähige Formulierung ist nicht „wikitool über MCP exponieren", sondern: chemenu
bekommt einen zweiten Konsumenten. CLI und MCP-Server sind zwei Adapter auf denselben Kern. run_search() (commands/search.py:82) und run_lint() (commands/lint.py:77) sind bereits
weitgehend reine Funktionen; was fehlt, ist die Bibliotheksgrenze darunter — das ist #31 und
Voraussetzung hierfür.
Oberfläche
Tool
Quelle
Anmerkung
search
run_search, inkl. --field-Prädikaten
types list / types describe
types_cmd
lint
run_lint
nur die JSON-Form; ohne Flag schreibt lint einen Report nach reports/ (lint.py:457-467)
status
komponiert
Es gibt keinwikitool status. wiki-status ist ein Skill, der kb/index.md + lint + kb/log.md zusammensetzt — der MCP-status ist eine neue, serverseitig komponierte Oberfläche, kein Wrapper.
Explizit nicht exponiert: new, touch, xref, cite, publish, migrate, version bump, alles unter work/. Strukturell — die Funktionen existieren im Servercode gar
nicht —, nicht als Prompt-Regel.
Wire-Contract sind exakt die --json-Formen der CLI (search --json, lint --json, types list --json). Ein Contract, nicht zwei; die CLI wird zur ausführbaren Spezifikation, ein
Golden-Test hält beide gegeneinander.
Direktimport, nicht Subprocess
Der Iteration Budget Gate zählt nicht, was man erwartet (run_budget.py:109):
SKIP_COMMANDS={"search","doctor"}
search und doctor sind befreit — lint und typesnicht. Ein Subprocess-Server ohne
Session-Injektion teilt sich über den getppid()-Fallback einen Zähler und steht nach 60 lint/types-Aufrufen. Mit Session-Injektion pro Anfrage ist das Gate umgekehrt bedeutungslos.
Beide Enden sind falsch.
Dazu: „strukturell, nicht Prompt-Regel" hält beim Subprocess nur mit fester argv-Allowlist —
etwa so viel Arbeit wie der Direktimport, ohne dessen Vorteile.
Fallen beim Direktimport:
_util.fail() (_util.py:41-47) wirft typer.Exit und setzt modulglobalen Zustand. Teil von #31.
WIKI_TRACE ist per Default an und schreibt Traces in den Checkout (telemetry/writer.py:35) —
also pro Anfrage, in genau das Verzeichnis, das der Sync per git reset --hard sauber hält. WIKI_TRACE_DIR nach außen legen oder abschalten.
Der residente Prozess ist zugleich die Performance-Lösung
Ein wikitool search kostet gemessen 593 ms: 262 ms Modulimport, 274 ms Korpus-Parse
(176 Seiten), 7 ms ripgrep. Ein Server zahlt den Import einmal pro Prozessstart statt pro
Aufruf. Zusammen mit dem Korpus-Cache und CSafeLoader aus #33 bleiben davon grob 100 ms.
Derselbe residente Prozess ist später der Ort, an dem ein Vektorindex lebt (#35) — ein
Embedding-Modell pro CLI-Aufruf zu laden wäre absurd.
Budget-Gate gehört nicht in den Server
Der Gate existiert, um eine Agenten-Session am unbemerkten Iterieren über den Zustand des
Wikis zu hindern; deshalb ist Retrieval befreit. Ein Nutzer, der zu oft sucht, ist ein Ressourcen-Problem — anderes Instrument, anderer Zweck. Beides zu vermischen würde den Gate zu
einem Rate Limiter verwässern.
Ressourcenschutz gehört damit an zwei Stellen, keine davon ist der Budget-Gate:
Vor dem Prozess: Rate Limiting in Traefik, neben der Authentifizierung.
Im Prozess: die Limits aus #33 (Subprozess-Timeout, Regex, YAML-Grenzen) — sie schützen
gegen einen authentifizierten Konsumenten, der sich selbst versehentlich beschädigt.
Session-Scoping wird erst mit dem Schreibpfad real (#32), wo eine Einreichung zurechenbar ist.
Staleness
search liest den Arbeitsbaum. Ein veralteter Checkout antwortet selbstbewusst falsch — genau
der Fehler, den SOUL.md als Kardinalfehler benennt.
Jede Antwort trägt den ausgelieferten Commit ({"commit": "<sha>", "as_of": "<iso>"}). Damit
wird aus einer stillen veralteten Antwort eine sichtbare. Derselbe SHA ist der Cache-Key aus #33
— ein Schlüssel für beides.
Sync: Polling. git fetch && git reset --hard origin/main alle paar Minuten braucht keinen
eingehenden Endpunkt und keine Signaturprüfung. Ein Webhook ist eine spätere Optimierung.
#15 (Recherche-Fähigkeit) läuft in die Gegenrichtung: ein Agent ohne eigene Websuche recherchiert
über eine externe API und landet das Ergebnis unverifiziert in raw/research/ (push,
kostenpflichtig, nicht-deterministisch). Dieses Vorhaben liest bestätigtes kb/-Wissen (pull,
deterministisch). Beide unabhängig umsetzbar, bewusst nicht dieselbe Oberfläche.
Offene Fragen
Transport: stdio, SSE oder streamable HTTP?
Wird der Server im Repo entwickelt oder als eigenes Artefakt, das chemenu als Abhängigkeit zieht?
Akzeptanzkriterien
Server exponiert search/types/lint/status, keine Schreib-Tools — weil die Funktionen
dort nicht existieren, nicht weil sie gefiltert werden
Ein Test beweist, dass kein Pfad des Servers nach kb/, reports/ oder in git schreibt
Antwortformate sind die --json-Formen der CLI; Golden-Test hält beide zusammen
Jede Antwort trägt Commit-SHA und Zeitstempel
Korpus-Cache, invalidiert am SHA (gemeinsam mit #33)
Traefik-Middleware für Auth und Rate Limiting steht und ist gegengeprüft: ein Aufruf ohne
gültige Credentials erreicht den Prozess nachweislich nicht
WIKI_TRACE_DIR zeigt aus dem Arbeitsbaum heraus, oder Tracing ist abgeschaltet
Sync-Mechanismus definiert und dokumentiert
Deployment dieser Instanz gehört nicht hierher — private Infrastruktur, siehe Kommentar
Hängt ab von:#31 (Bibliotheksgrenze), #33 (Härtung des Lesepfads). Blockiert:#32 (Ingest-Queue). Verwandt:#35 (semantisches Backend im selben Prozess).
Ein MCP-Server ist **beschlossen**, nicht mehr bedingt: es wird mehrere Konsumenten geben, der
Perplexity Space war nur der erste. Zwei Grundsatzfragen sind am 2026-09-01 entschieden und
stehen hier nicht mehr zur Debatte (Herleitung im Kommentarverlauf):
- **Plattform: Python.** Kein Go, auch nicht als Option.
- **Authentifizierung: Traefik-Middleware.** Nicht sauber authentifizierte Zugriffe erreichen den
Python-Prozess nicht.
## Befund
`wikitool` ist ein lokales CLI ohne Netzwerkschnittstelle. Der gitea-mcp-Connector bildet nur die
Gitea-REST-API ab, keinen Ausführungs-Endpunkt. Ohne eigene Ausführungsschicht kann ein externer
Konsument nur über einzelne `get_file_contents`-Aufrufe lesen — langsamer und ungenauer als
`wikitool search`.
Die tragfähige Formulierung ist nicht „wikitool über MCP exponieren", sondern: **`chemenu`
bekommt einen zweiten Konsumenten.** CLI und MCP-Server sind zwei Adapter auf denselben Kern.
`run_search()` (`commands/search.py:82`) und `run_lint()` (`commands/lint.py:77`) sind bereits
weitgehend reine Funktionen; was fehlt, ist die Bibliotheksgrenze darunter — das ist #31 und
Voraussetzung hierfür.
## Oberfläche
| Tool | Quelle | Anmerkung |
|---|---|---|
| `search` | `run_search`, inkl. `--field`-Prädikaten | |
| `types list` / `types describe` | `types_cmd` | |
| `lint` | `run_lint` | **nur** die JSON-Form; ohne Flag schreibt `lint` einen Report nach `reports/` (`lint.py:457-467`) |
| `status` | komponiert | Es gibt **kein** `wikitool status`. `wiki-status` ist ein Skill, der `kb/index.md` + `lint` + `kb/log.md` zusammensetzt — der MCP-`status` ist eine neue, serverseitig komponierte Oberfläche, kein Wrapper. |
Explizit **nicht** exponiert: `new`, `touch`, `xref`, `cite`, `publish`, `migrate`,
`version bump`, alles unter `work/`. Strukturell — die Funktionen existieren im Servercode gar
nicht —, nicht als Prompt-Regel.
Wire-Contract sind **exakt die `--json`-Formen der CLI** (`search --json`, `lint --json`,
`types list --json`). Ein Contract, nicht zwei; die CLI wird zur ausführbaren Spezifikation, ein
Golden-Test hält beide gegeneinander.
## Direktimport, nicht Subprocess
Der Iteration Budget Gate zählt nicht, was man erwartet (`run_budget.py:109`):
```python
SKIP_COMMANDS = {"search", "doctor"}
```
`search` und `doctor` sind befreit — `lint` und `types` **nicht**. Ein Subprocess-Server ohne
Session-Injektion teilt sich über den `getppid()`-Fallback einen Zähler und steht nach 60
`lint`/`types`-Aufrufen. Mit Session-Injektion pro Anfrage ist das Gate umgekehrt bedeutungslos.
Beide Enden sind falsch.
Dazu: „strukturell, nicht Prompt-Regel" hält beim Subprocess nur mit fester argv-Allowlist —
etwa so viel Arbeit wie der Direktimport, ohne dessen Vorteile.
Fallen beim Direktimport:
- `_util.fail()` (`_util.py:41-47`) wirft `typer.Exit` und setzt modulglobalen Zustand. Teil von #31.
- `WIKI_TRACE` ist per Default an und schreibt Traces in den Checkout (`telemetry/writer.py:35`) —
also pro Anfrage, in genau das Verzeichnis, das der Sync per `git reset --hard` sauber hält.
`WIKI_TRACE_DIR` nach außen legen oder abschalten.
## Der residente Prozess ist zugleich die Performance-Lösung
Ein `wikitool search` kostet gemessen 593 ms: 262 ms Modulimport, 274 ms Korpus-Parse
(176 Seiten), 7 ms ripgrep. Ein Server zahlt den Import **einmal pro Prozessstart statt pro
Aufruf**. Zusammen mit dem Korpus-Cache und `CSafeLoader` aus #33 bleiben davon grob 100 ms.
Derselbe residente Prozess ist später der Ort, an dem ein Vektorindex lebt (#35) — ein
Embedding-Modell pro CLI-Aufruf zu laden wäre absurd.
## Budget-Gate gehört nicht in den Server
Der Gate existiert, um eine **Agenten-Session** am unbemerkten Iterieren über den Zustand des
Wikis zu hindern; deshalb ist Retrieval befreit. Ein Nutzer, der zu oft sucht, ist ein
*Ressourcen*-Problem — anderes Instrument, anderer Zweck. Beides zu vermischen würde den Gate zu
einem Rate Limiter verwässern.
Ressourcenschutz gehört damit an zwei Stellen, keine davon ist der Budget-Gate:
- **Vor dem Prozess:** Rate Limiting in Traefik, neben der Authentifizierung.
- **Im Prozess:** die Limits aus #33 (Subprozess-Timeout, Regex, YAML-Grenzen) — sie schützen
gegen einen authentifizierten Konsumenten, der sich selbst versehentlich beschädigt.
Session-Scoping wird erst mit dem Schreibpfad real (#32), wo eine Einreichung zurechenbar ist.
## Staleness
`search` liest den Arbeitsbaum. Ein veralteter Checkout antwortet selbstbewusst falsch — genau
der Fehler, den `SOUL.md` als Kardinalfehler benennt.
**Jede Antwort trägt den ausgelieferten Commit** (`{"commit": "<sha>", "as_of": "<iso>"}`). Damit
wird aus einer stillen veralteten Antwort eine sichtbare. Derselbe SHA ist der Cache-Key aus #33
— ein Schlüssel für beides.
Sync: Polling. `git fetch && git reset --hard origin/main` alle paar Minuten braucht keinen
eingehenden Endpunkt und keine Signaturprüfung. Ein Webhook ist eine spätere Optimierung.
## Abgrenzung zu #15
#15 (Recherche-Fähigkeit) läuft in die Gegenrichtung: ein Agent ohne eigene Websuche recherchiert
über eine externe API und landet das Ergebnis unverifiziert in `raw/research/` (push,
kostenpflichtig, nicht-deterministisch). Dieses Vorhaben liest bestätigtes `kb/`-Wissen (pull,
deterministisch). Beide unabhängig umsetzbar, bewusst nicht dieselbe Oberfläche.
## Offene Fragen
- Transport: stdio, SSE oder streamable HTTP?
- Wird der Server im Repo entwickelt oder als eigenes Artefakt, das `chemenu` als Abhängigkeit zieht?
## Akzeptanzkriterien
- [ ] Server exponiert `search`/`types`/`lint`/`status`, keine Schreib-Tools — weil die Funktionen
dort nicht existieren, nicht weil sie gefiltert werden
- [ ] Ein Test beweist, dass kein Pfad des Servers nach `kb/`, `reports/` oder in git schreibt
- [ ] Antwortformate sind die `--json`-Formen der CLI; Golden-Test hält beide zusammen
- [ ] Jede Antwort trägt Commit-SHA und Zeitstempel
- [ ] Korpus-Cache, invalidiert am SHA (gemeinsam mit #33)
- [ ] Traefik-Middleware für Auth und Rate Limiting steht und ist gegengeprüft: ein Aufruf ohne
gültige Credentials erreicht den Prozess nachweislich nicht
- [ ] `WIKI_TRACE_DIR` zeigt aus dem Arbeitsbaum heraus, oder Tracing ist abgeschaltet
- [ ] Sync-Mechanismus definiert und dokumentiert
- [ ] Deployment dieser Instanz gehört **nicht** hierher — private Infrastruktur, siehe Kommentar
**Hängt ab von:** #31 (Bibliotheksgrenze), #33 (Härtung des Lesepfads).
**Blockiert:** #32 (Ingest-Queue). **Verwandt:** #35 (semantisches Backend im selben Prozess).
torben
changed title from Read-only MCP-Frontend für wikitool (search/status/types/lint) an Perplexity Space koppeln to MCP-Leseserver für chemenu: search/types/lint/status als Tools, ohne Schreibpfad nach kb/2026-09-01 20:00:47 +00:00
torben
added size/M and removed size/L labels 2026-09-01 20:00:52 +00:00
search ist heute zweigeteilt: commands/search.py (CLI) und chemenu/search/ (Backends, Filter, Fusion). Die Liste zeigt auf eine Struktur von vor dem Split.
„ADR-002 / kb/concepts/External Ingress Model.md"
Existiert nicht mehr. wikitool search findet weder „External Ingress" noch „ADR-002" noch „Abydos" — der Korpus-Purge vom 2026-09-01 hat die private Infrastruktur-Hälfte entfernt.
Es gibt kein wikitool status. Steht jetzt als eigener Punkt im Text.
Deployment-Teil entfallen. Abydos, hermes, Traefik-Label, MetalLB, 1Password/ESO,
k3s-Namespace: das ist private Infrastruktur in einem öffentlichen Issue-Tracker. USER.md („Keine privaten Infrastrukturdaten") und ENVIRONMENT.md ziehen diese Grenze
ausdrücklich, der alte Issue-Text zog sie nicht. Was hier bleibt, ist die Schnittstelle; wo sie läuft, gehört in die private Instanz.
Aufgeteilt in drei Pakete. Als ein size/L wäre das nie angefasst worden:
Paket
Issue
Größe
A — Root-Auflösung konfigurierbar, Bibliotheksgrenze ohne typer.Exit
Fachlich neu im Text: der Befund zum Iteration Budget Gate (search/doctor befreit, lint/types nicht — ein Subprocess-Server läuft damit nach 60 Aufrufen tot), die
Empfehlung Direktimport statt Subprocess mit Begründung, der Commit-Stempel in jeder
Antwort als Mittel gegen stille Staleness, und die Feststellung, dass Session-Scoping für
die Leseoberfläche das falsche Instrument ist (Rate Limit statt Budget-Gate).
Relabel size/L → size/M: die offenen Designfragen, die das L begründet haben
(Subprocess vs. Direktimport, Sync-Mechanismus, Session-Scoping), sind beantwortet, und
der Schreibpfad ist nach #32 abgeflossen. Was übrig bleibt, ist mehrere Dateien plus
eigener Testaufwand.
prio/3 bleibt, mit jetzt benanntem Auslöser: ein Konsument, der die Oberfläche
tatsächlich braucht.
Neu geschnitten am 2026-09-01, nach Prüfung gegen den heutigen Code. Was sich geändert
hat und warum:
**Was am alten Text nicht mehr stimmte**
| Stelle | Problem |
|---|---|
| „importiert `tools/wiki_tools`-Module direkt" | Paket heißt seit dem Rename `tools/chemenu/`. Instanz von #29. |
| Modulliste `search.py, kb_state.py, type_resolver.py` | `search` ist heute zweigeteilt: `commands/search.py` (CLI) und `chemenu/search/` (Backends, Filter, Fusion). Die Liste zeigt auf eine Struktur von vor dem Split. |
| „ADR-002 / `kb/concepts/External Ingress Model.md`" | Existiert nicht mehr. `wikitool search` findet weder „External Ingress" noch „ADR-002" noch „Abydos" — der Korpus-Purge vom 2026-09-01 hat die private Infrastruktur-Hälfte entfernt. |
| Akzeptanzkriterium „exponiert search/**status**/types/lint" | Es gibt kein `wikitool status`. Steht jetzt als eigener Punkt im Text. |
**Deployment-Teil entfallen.** Abydos, hermes, Traefik-Label, MetalLB, 1Password/ESO,
k3s-Namespace: das ist private Infrastruktur in einem öffentlichen Issue-Tracker.
`USER.md` („Keine privaten Infrastrukturdaten") und `ENVIRONMENT.md` ziehen diese Grenze
ausdrücklich, der alte Issue-Text zog sie nicht. Was hier bleibt, ist die Schnittstelle;
*wo* sie läuft, gehört in die private Instanz.
**Aufgeteilt in drei Pakete.** Als ein `size/L` wäre das nie angefasst worden:
| Paket | Issue | Größe |
|---|---|---|
| A — Root-Auflösung konfigurierbar, Bibliotheksgrenze ohne `typer.Exit` | #31 | `size/S` |
| B — MCP-Leseserver (dieses Issue) | #19 | `size/M` |
| C — Ingest-Queue (Schreibpfad) | #32 | `size/M` |
**Fachlich neu im Text:** der Befund zum Iteration Budget Gate (`search`/`doctor` befreit,
`lint`/`types` nicht — ein Subprocess-Server läuft damit nach 60 Aufrufen tot), die
Empfehlung Direktimport statt Subprocess mit Begründung, der Commit-Stempel in jeder
Antwort als Mittel gegen stille Staleness, und die Feststellung, dass Session-Scoping für
die Leseoberfläche das falsche Instrument ist (Rate Limit statt Budget-Gate).
**Relabel `size/L` → `size/M`:** die offenen Designfragen, die das L begründet haben
(Subprocess vs. Direktimport, Sync-Mechanismus, Session-Scoping), sind beantwortet, und
der Schreibpfad ist nach #32 abgeflossen. Was übrig bleibt, ist mehrere Dateien plus
eigener Testaufwand.
`prio/3` bleibt, mit jetzt benanntem Auslöser: ein Konsument, der die Oberfläche
tatsächlich braucht.
Plattformentscheidung 2026-09-01: Python bleibt, Go ist vom Tisch
Ausgelöst durch die Frage, ob Python für einen netzexponierten Dienst security-seitig noch
tragfähig ist, und ob wikitool vollständig nach Go migriert werden sollte. Zwei
Mehr-Perspektiven-Debatten mit neun Positionen, alle tragenden Befunde in der Sitzung selbst
nachgemessen. Ergebnis: Python, kein Go, auch nicht als Option.
Die Begründung, kurz — damit niemand die Frage in sechs Monaten neu aufmacht:
Sicherheit trägt die Migration nicht. Von zehn Befunden am Lesepfad waren zwei
sprachabhängig (ReDoS, YAML-Alias-Budget), beide mit wenigen Zeilen in Python behebbar — jetzt #33. Die teuersten Befunde waren Isolation und Konfiguration, und die sind sprachneutral.
Die drei Argumente, die für Go sprachen, lösen sich am eigenen Vorhaben auf:
Startkosten (262 ms Import pro Aufruf): ein residenter MCP-Server zahlt sie einmal pro
Prozessstart. Das stärkste gemessene Go-Argument verschwindet durch die Entscheidung, die in
diesem Issue ohnehin gefallen ist.
Korpus-Parse (274 ms, skaliert linear): CSafeLoader ist verfügbar und dreimal schneller
(246 → 82 ms), plus Cache — #33.
Statisches Binary für die Distribution: überlebt Index und Suche, stirbt an der
Embedding-Schicht. Lokale Embedding-Erzeugung gibt es in reinem Go nicht; der Weg führt über
ONNX oder llama.cpp, also cgo plus Shared Library plus Cross-Toolchain pro Zielplattform.
Ausgerechnet dort ist Python durch vorkompilierte Wheels besser aufgestellt. Go läuft an dieser
Stelle gegen sein eigenes bestes Argument.
Das Vorbild widerlegt die These.qmd ist entgegen kb/entities/tools/qmd.md nicht Go oder
Rust, sondern TypeScript/Bun (verifiziert gegen das Repository): SQLite FTS5 + sqlite-vec +
node-llama-cpp. Das Werkzeug, dessen Fähigkeit hier gewünscht ist, erreicht sie aus einer
Skriptsprache heraus über native Bibliotheken — dasselbe Muster, das Chemenu mit ripgrep seit
Tag eins praktiziert. Die Korrektur der Seite läuft separat über wiki-manage.
Der Terminkonflikt gab den Ausschlag.instructions/private-instance.md bezieht
Stack-Updates per git merge upstream/main unter dem Leitsatz „Take the machinery, never the
content". Eine Portierung ersetzt genau diese Maschinerie; jeder künftige Update-Merge in die
private Instanz aus #30 wäre ein Totalkonflikt in tools/, und die Auflösungsregel „Konflikt in tools/? Nimm die Upstream-Seite" hieße: nimm eine Sprache, für die dein Checkout keine Laufzeit
hat. Die private Instanz entsteht jetzt.
Zur Vollständigkeit, weil es fair ist: eine agentengeführte Portierung wäre ausführbar
gewesen — geschätzt 25–40 Sitzungen, differentielle Testbank gegen die bestehende Implementierung
als Orakel, mit der Regel „das Go-Binary ist bis zum letzten Schnitt nur Diff-Partner", die den
Zustand zweier halbportierter Implementierungen strukturell ausschließt. Die Entscheidung fällt
also nicht gegen die Machbarkeit, sondern gegen den Nutzen.
Authentifizierung: Traefik-Middleware
Ebenfalls entschieden. Auth wird als Traefik-Middleware vor dem Dienst abgebildet; nicht sauber
authentifizierte Zugriffe erreichen den Python-Prozess nicht. Konsequenzen, im Text
eingearbeitet:
Der Auth-Mechanismus ist keine offene Frage dieses Issues mehr.
Rate Limiting gehört an dieselbe Stelle — vor den Prozess, nicht in den Budget-Gate.
Die In-Prozess-Limits aus #33 bleiben trotzdem nötig: sie schützen nicht gegen Anonyme, sondern
gegen einen authentifizierten Konsumenten, der sich selbst versehentlich beschädigt.
Relabel prio/3 → prio/2
Der Auslöser, den ich beim letzten Neuschnitt als Bedingung notiert hatte („ein Konsument, der
die Oberfläche tatsächlich braucht"), ist eingetreten: der Server ist beschlossen, mehrere
Konsumenten sind absehbar. Damit ist es geplante Arbeit, kein wartender Vorschlag mehr.
## Plattformentscheidung 2026-09-01: Python bleibt, Go ist vom Tisch
Ausgelöst durch die Frage, ob Python für einen netzexponierten Dienst security-seitig noch
tragfähig ist, und ob `wikitool` vollständig nach Go migriert werden sollte. Zwei
Mehr-Perspektiven-Debatten mit neun Positionen, alle tragenden Befunde in der Sitzung selbst
nachgemessen. Ergebnis: **Python, kein Go, auch nicht als Option.**
Die Begründung, kurz — damit niemand die Frage in sechs Monaten neu aufmacht:
**Sicherheit trägt die Migration nicht.** Von zehn Befunden am Lesepfad waren zwei
sprachabhängig (ReDoS, YAML-Alias-Budget), beide mit wenigen Zeilen in Python behebbar — jetzt
#33. Die teuersten Befunde waren Isolation und Konfiguration, und die sind sprachneutral.
**Die drei Argumente, die für Go sprachen, lösen sich am eigenen Vorhaben auf:**
- *Startkosten* (262 ms Import pro Aufruf): ein residenter MCP-Server zahlt sie einmal pro
Prozessstart. Das stärkste gemessene Go-Argument verschwindet durch die Entscheidung, die in
diesem Issue ohnehin gefallen ist.
- *Korpus-Parse* (274 ms, skaliert linear): `CSafeLoader` ist verfügbar und dreimal schneller
(246 → 82 ms), plus Cache — #33.
- *Statisches Binary für die Distribution*: überlebt Index und Suche, stirbt an der
Embedding-Schicht. Lokale Embedding-Erzeugung gibt es in reinem Go nicht; der Weg führt über
ONNX oder llama.cpp, also cgo plus Shared Library plus Cross-Toolchain pro Zielplattform.
Ausgerechnet dort ist Python durch vorkompilierte Wheels besser aufgestellt. Go läuft an dieser
Stelle gegen sein eigenes bestes Argument.
**Das Vorbild widerlegt die These.** `qmd` ist entgegen `kb/entities/tools/qmd.md` nicht Go oder
Rust, sondern **TypeScript/Bun** (verifiziert gegen das Repository): SQLite FTS5 + sqlite-vec +
node-llama-cpp. Das Werkzeug, dessen Fähigkeit hier gewünscht ist, erreicht sie aus einer
Skriptsprache heraus über native Bibliotheken — dasselbe Muster, das Chemenu mit `ripgrep` seit
Tag eins praktiziert. Die Korrektur der Seite läuft separat über `wiki-manage`.
**Der Terminkonflikt gab den Ausschlag.** `instructions/private-instance.md` bezieht
Stack-Updates per `git merge upstream/main` unter dem Leitsatz „Take the machinery, never the
content". Eine Portierung ersetzt genau diese Maschinerie; jeder künftige Update-Merge in die
private Instanz aus #30 wäre ein Totalkonflikt in `tools/`, und die Auflösungsregel „Konflikt in
`tools/`? Nimm die Upstream-Seite" hieße: nimm eine Sprache, für die dein Checkout keine Laufzeit
hat. Die private Instanz entsteht jetzt.
Zur Vollständigkeit, weil es fair ist: eine agentengeführte Portierung wäre **ausführbar**
gewesen — geschätzt 25–40 Sitzungen, differentielle Testbank gegen die bestehende Implementierung
als Orakel, mit der Regel „das Go-Binary ist bis zum letzten Schnitt nur Diff-Partner", die den
Zustand zweier halbportierter Implementierungen strukturell ausschließt. Die Entscheidung fällt
also nicht gegen die Machbarkeit, sondern gegen den Nutzen.
## Authentifizierung: Traefik-Middleware
Ebenfalls entschieden. Auth wird als Traefik-Middleware vor dem Dienst abgebildet; nicht sauber
authentifizierte Zugriffe erreichen den Python-Prozess nicht. Konsequenzen, im Text
eingearbeitet:
- Der Auth-Mechanismus ist keine offene Frage dieses Issues mehr.
- Rate Limiting gehört an dieselbe Stelle — vor den Prozess, nicht in den Budget-Gate.
- Die In-Prozess-Limits aus #33 bleiben trotzdem nötig: sie schützen nicht gegen Anonyme, sondern
gegen einen authentifizierten Konsumenten, der sich selbst versehentlich beschädigt.
## Relabel `prio/3` → `prio/2`
Der Auslöser, den ich beim letzten Neuschnitt als Bedingung notiert hatte („ein Konsument, der
die Oberfläche tatsächlich braucht"), ist eingetreten: der Server ist beschlossen, mehrere
Konsumenten sind absehbar. Damit ist es geplante Arbeit, kein wartender Vorschlag mehr.
Teil der Sequenz in #36 (Master: Weg zum MCP-Leseserver) — dort Schritt 3 von 3, nach #33 und #31. Das Master-Issue hält die getroffenen Entscheidungen, die Messungen und die verifizierten
Befunde gebündelt, damit sie nicht über mehrere Kommentarverläufe verstreut nachgelesen werden
müssen.
Teil der Sequenz in **#36** (Master: Weg zum MCP-Leseserver) — dort Schritt 3 von 3, nach #33 und
#31. Das Master-Issue hält die getroffenen Entscheidungen, die Messungen und die verifizierten
Befunde gebündelt, damit sie nicht über mehrere Kommentarverläufe verstreut nachgelesen werden
müssen.
Die beiden offenen Fragen sind entschieden (Betreiber, 2026-09-02):
Transport: beide.stdio zum Entwickeln und Testen ohne Netz, streamable-http für die
Auslieferung — der einzige, vor den sich die Traefik-Middleware setzen kann, weil sie ein
HTTP-Reverse-Proxy ist. sse ist über das SDK erreichbar und wird nicht angeboten: der
abgelöste Remote-Transport, jetzt darauf zu bauen verschiebt den Wechsel nur.
Im Repo, nicht als eigenes Artefakt. Der Golden-Test läuft nur mit beiden Seiten in einer
Testsuite; getrennt würde aus einem Contract eine Versionsabsprache.
Oberfläche:search, types, describe_type, lint, status. status ist wie
beschrieben serverseitig komponiert und kein Wrapper.
Akzeptanzkriterien:
Kein Schreibpfad — strukturell. Weder der Server noch chemenu.api importiert etwas
unter chemenu.commands; ein Test importiert das Servermodul in einem frischen Interpreter
und prüft sys.modules.
Ein Test beweist, dass nichts nach kb/, reports/ oder in git schreibt: alle fünf Tools
werden aufgerufen, danach Dateibaum (Größe und Inhalt), HEAD und git status --porcelain gegen vorher verglichen; zusätzlich, dass reports/ gar nicht
erst entsteht.
Antwortformate sind die --json-Formen der CLI, Golden-Test hält beide zusammen. Er ruft wikitool als Subprozess gegen denselben Baum auf, über CHEMENU_ROOT — womit er nebenbei
die Root-Auflösung von außen mitprüft. Für search und für beide types-Formen.
WIKI_TRACE_DIR zeigt aus dem Arbeitsbaum heraus — erzwungen: der Server verweigert
den Start, wenn Telemetrie an ist und in den bedienten Checkout schreiben würde, statt
still umzuleiten. Heute emittiert auf diesem Pfad nichts (der Emitter hängt an cli.main()
und den Gates), die Sperre ist gegen später.
Sync-Mechanismus definiert und dokumentiert: Polling
(git fetch && git reset --hard origin/main) in instructions/mcp-read-server.md. reset --hard ist dort tragend — ein abgedrifteter Baum antwortet zwar richtig, parst aber
bei jeder Anfrage neu und stempelt jede Antwort mit null.
Was nicht Teil davon war und offen bleibt: die Middleware selbst steht
(https://gitea.nehmer.net/torben/gitea-mcp-forward-auth), aber ein Aufruf ohne gültige
Credentials wurde gegen diesen Server noch nicht nachweislich abgewiesen — dafür braucht es
ein Deployment. Das hängt am Container-Image, das jetzt #37 ist.
Zum Budget-Gate: wie im Issue argumentiert nicht im Server. Ressourcenschutz im Prozess sind
die Limits aus #33 (Subprozess-Timeout, Regex, YAML-Grenzen).
Befund 8 aus #36 (lint liest raw/-Dateinamen, lint.py:230) war hier zu entscheiden: lint ist unverändert exponiert, inklusive uncovered_raw_files. Für diese Instanz ist das
folgenlos — ein Konsument sieht die Dateinamen eines öffentlichen Korpus. Es wird relevant, sobald
mehrere Einreicher existieren, und das ist #32; dort gehört die Entscheidung hin, nicht hierher,
wo sie ohne den Schreibpfad nichts zu schützen hätte.
Gemessen und gefahren: beide Transports gegen den echten Korpus (176 Seiten, korrekter
HEAD-Stempel); wikitool search end-to-end von 593 ms auf 347 ms (aus #33), die verbleibenden
~262 ms Modulimport entfallen im residenten Prozess.
Umgesetzt in **2.4.0**. `tools/chemenu/mcp/`.
**Die beiden offenen Fragen sind entschieden** (Betreiber, 2026-09-02):
- **Transport: beide.** `stdio` zum Entwickeln und Testen ohne Netz, `streamable-http` für die
Auslieferung — der einzige, vor den sich die Traefik-Middleware setzen kann, weil sie ein
HTTP-Reverse-Proxy ist. `sse` ist über das SDK erreichbar und wird **nicht** angeboten: der
abgelöste Remote-Transport, jetzt darauf zu bauen verschiebt den Wechsel nur.
- **Im Repo**, nicht als eigenes Artefakt. Der Golden-Test läuft nur mit beiden Seiten in einer
Testsuite; getrennt würde aus einem Contract eine Versionsabsprache.
**Oberfläche:** `search`, `types`, `describe_type`, `lint`, `status`. `status` ist wie
beschrieben serverseitig **komponiert** und kein Wrapper.
**Akzeptanzkriterien:**
- [x] Kein Schreibpfad — **strukturell**. Weder der Server noch `chemenu.api` importiert etwas
unter `chemenu.commands`; ein Test importiert das Servermodul in einem frischen Interpreter
und prüft `sys.modules`.
- [x] Ein Test beweist, dass nichts nach `kb/`, `reports/` oder in git schreibt: alle fünf Tools
werden aufgerufen, danach Dateibaum (Größe **und** Inhalt), `HEAD` und
`git status --porcelain` gegen vorher verglichen; zusätzlich, dass `reports/` gar nicht
erst entsteht.
- [x] Antwortformate sind die `--json`-Formen der CLI, Golden-Test hält beide zusammen. Er ruft
`wikitool` als Subprozess gegen denselben Baum auf, über `CHEMENU_ROOT` — womit er nebenbei
die Root-Auflösung von außen mitprüft. Für `search` und für beide `types`-Formen.
- [x] Jede Antwort trägt `commit` und `as_of`.
- [x] Korpus-Cache, invalidiert am SHA (kam mit #33).
- [x] `WIKI_TRACE_DIR` zeigt aus dem Arbeitsbaum heraus — **erzwungen**: der Server verweigert
den Start, wenn Telemetrie an ist und in den bedienten Checkout schreiben würde, statt
still umzuleiten. Heute emittiert auf diesem Pfad nichts (der Emitter hängt an `cli.main()`
und den Gates), die Sperre ist gegen später.
- [x] Sync-Mechanismus definiert und dokumentiert: Polling
(`git fetch && git reset --hard origin/main`) in `instructions/mcp-read-server.md`.
`reset --hard` ist dort tragend — ein abgedrifteter Baum antwortet zwar richtig, parst aber
bei jeder Anfrage neu und stempelt jede Antwort mit `null`.
- [x] Deployment nicht hier.
- [ ] **Traefik-Middleware gegengeprüft** — offen, siehe unten.
**Was nicht Teil davon war und offen bleibt:** die Middleware selbst steht
(<https://gitea.nehmer.net/torben/gitea-mcp-forward-auth>), aber ein Aufruf ohne gültige
Credentials wurde gegen *diesen* Server noch nicht nachweislich abgewiesen — dafür braucht es
ein Deployment. Das hängt am Container-Image, das jetzt **#37** ist.
**Zum Budget-Gate:** wie im Issue argumentiert nicht im Server. Ressourcenschutz im Prozess sind
die Limits aus #33 (Subprozess-Timeout, Regex, YAML-Grenzen).
**Befund 8 aus #36** (`lint` liest `raw/`-Dateinamen, `lint.py:230`) war hier zu entscheiden:
`lint` ist unverändert exponiert, inklusive `uncovered_raw_files`. Für diese Instanz ist das
folgenlos — ein Konsument sieht die Dateinamen eines öffentlichen Korpus. Es wird relevant, sobald
mehrere Einreicher existieren, und das ist #32; dort gehört die Entscheidung hin, nicht hierher,
wo sie ohne den Schreibpfad nichts zu schützen hätte.
**Gemessen und gefahren:** beide Transports gegen den echten Korpus (176 Seiten, korrekter
HEAD-Stempel); `wikitool search` end-to-end von 593 ms auf 347 ms (aus #33), die verbleibenden
~262 ms Modulimport entfallen im residenten Prozess.
Menschendoku: `INSTALL-MCP.md`. Betrieb: `instructions/mcp-read-server.md`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Ein MCP-Server ist beschlossen, nicht mehr bedingt: es wird mehrere Konsumenten geben, der
Perplexity Space war nur der erste. Zwei Grundsatzfragen sind am 2026-09-01 entschieden und
stehen hier nicht mehr zur Debatte (Herleitung im Kommentarverlauf):
Python-Prozess nicht.
Befund
wikitoolist ein lokales CLI ohne Netzwerkschnittstelle. Der gitea-mcp-Connector bildet nur dieGitea-REST-API ab, keinen Ausführungs-Endpunkt. Ohne eigene Ausführungsschicht kann ein externer
Konsument nur über einzelne
get_file_contents-Aufrufe lesen — langsamer und ungenauer alswikitool search.Die tragfähige Formulierung ist nicht „wikitool über MCP exponieren", sondern:
chemenubekommt einen zweiten Konsumenten. CLI und MCP-Server sind zwei Adapter auf denselben Kern.
run_search()(commands/search.py:82) undrun_lint()(commands/lint.py:77) sind bereitsweitgehend reine Funktionen; was fehlt, ist die Bibliotheksgrenze darunter — das ist #31 und
Voraussetzung hierfür.
Oberfläche
searchrun_search, inkl.--field-Prädikatentypes list/types describetypes_cmdlintrun_lintlinteinen Report nachreports/(lint.py:457-467)statuswikitool status.wiki-statusist ein Skill, derkb/index.md+lint+kb/log.mdzusammensetzt — der MCP-statusist eine neue, serverseitig komponierte Oberfläche, kein Wrapper.Explizit nicht exponiert:
new,touch,xref,cite,publish,migrate,version bump, alles unterwork/. Strukturell — die Funktionen existieren im Servercode garnicht —, nicht als Prompt-Regel.
Wire-Contract sind exakt die
--json-Formen der CLI (search --json,lint --json,types list --json). Ein Contract, nicht zwei; die CLI wird zur ausführbaren Spezifikation, einGolden-Test hält beide gegeneinander.
Direktimport, nicht Subprocess
Der Iteration Budget Gate zählt nicht, was man erwartet (
run_budget.py:109):searchunddoctorsind befreit —lintundtypesnicht. Ein Subprocess-Server ohneSession-Injektion teilt sich über den
getppid()-Fallback einen Zähler und steht nach 60lint/types-Aufrufen. Mit Session-Injektion pro Anfrage ist das Gate umgekehrt bedeutungslos.Beide Enden sind falsch.
Dazu: „strukturell, nicht Prompt-Regel" hält beim Subprocess nur mit fester argv-Allowlist —
etwa so viel Arbeit wie der Direktimport, ohne dessen Vorteile.
Fallen beim Direktimport:
_util.fail()(_util.py:41-47) wirfttyper.Exitund setzt modulglobalen Zustand. Teil von #31.WIKI_TRACEist per Default an und schreibt Traces in den Checkout (telemetry/writer.py:35) —also pro Anfrage, in genau das Verzeichnis, das der Sync per
git reset --hardsauber hält.WIKI_TRACE_DIRnach außen legen oder abschalten.Der residente Prozess ist zugleich die Performance-Lösung
Ein
wikitool searchkostet gemessen 593 ms: 262 ms Modulimport, 274 ms Korpus-Parse(176 Seiten), 7 ms ripgrep. Ein Server zahlt den Import einmal pro Prozessstart statt pro
Aufruf. Zusammen mit dem Korpus-Cache und
CSafeLoaderaus #33 bleiben davon grob 100 ms.Derselbe residente Prozess ist später der Ort, an dem ein Vektorindex lebt (#35) — ein
Embedding-Modell pro CLI-Aufruf zu laden wäre absurd.
Budget-Gate gehört nicht in den Server
Der Gate existiert, um eine Agenten-Session am unbemerkten Iterieren über den Zustand des
Wikis zu hindern; deshalb ist Retrieval befreit. Ein Nutzer, der zu oft sucht, ist ein
Ressourcen-Problem — anderes Instrument, anderer Zweck. Beides zu vermischen würde den Gate zu
einem Rate Limiter verwässern.
Ressourcenschutz gehört damit an zwei Stellen, keine davon ist der Budget-Gate:
gegen einen authentifizierten Konsumenten, der sich selbst versehentlich beschädigt.
Session-Scoping wird erst mit dem Schreibpfad real (#32), wo eine Einreichung zurechenbar ist.
Staleness
searchliest den Arbeitsbaum. Ein veralteter Checkout antwortet selbstbewusst falsch — genauder Fehler, den
SOUL.mdals Kardinalfehler benennt.Jede Antwort trägt den ausgelieferten Commit (
{"commit": "<sha>", "as_of": "<iso>"}). Damitwird aus einer stillen veralteten Antwort eine sichtbare. Derselbe SHA ist der Cache-Key aus #33
— ein Schlüssel für beides.
Sync: Polling.
git fetch && git reset --hard origin/mainalle paar Minuten braucht keineneingehenden Endpunkt und keine Signaturprüfung. Ein Webhook ist eine spätere Optimierung.
Abgrenzung zu #15
#15 (Recherche-Fähigkeit) läuft in die Gegenrichtung: ein Agent ohne eigene Websuche recherchiert
über eine externe API und landet das Ergebnis unverifiziert in
raw/research/(push,kostenpflichtig, nicht-deterministisch). Dieses Vorhaben liest bestätigtes
kb/-Wissen (pull,deterministisch). Beide unabhängig umsetzbar, bewusst nicht dieselbe Oberfläche.
Offene Fragen
chemenuals Abhängigkeit zieht?Akzeptanzkriterien
search/types/lint/status, keine Schreib-Tools — weil die Funktionendort nicht existieren, nicht weil sie gefiltert werden
kb/,reports/oder in git schreibt--json-Formen der CLI; Golden-Test hält beide zusammengültige Credentials erreicht den Prozess nachweislich nicht
WIKI_TRACE_DIRzeigt aus dem Arbeitsbaum heraus, oder Tracing ist abgeschaltetHängt ab von: #31 (Bibliotheksgrenze), #33 (Härtung des Lesepfads).
Blockiert: #32 (Ingest-Queue). Verwandt: #35 (semantisches Backend im selben Prozess).
Read-only MCP-Frontend für wikitool (search/status/types/lint) an Perplexity Space koppelnto MCP-Leseserver für chemenu: search/types/lint/status als Tools, ohne Schreibpfad nach kb/Neu geschnitten am 2026-09-01, nach Prüfung gegen den heutigen Code. Was sich geändert
hat und warum:
Was am alten Text nicht mehr stimmte
tools/wiki_tools-Module direkt"tools/chemenu/. Instanz von #29.search.py, kb_state.py, type_resolver.pysearchist heute zweigeteilt:commands/search.py(CLI) undchemenu/search/(Backends, Filter, Fusion). Die Liste zeigt auf eine Struktur von vor dem Split.kb/concepts/External Ingress Model.md"wikitool searchfindet weder „External Ingress" noch „ADR-002" noch „Abydos" — der Korpus-Purge vom 2026-09-01 hat die private Infrastruktur-Hälfte entfernt.wikitool status. Steht jetzt als eigener Punkt im Text.Deployment-Teil entfallen. Abydos, hermes, Traefik-Label, MetalLB, 1Password/ESO,
k3s-Namespace: das ist private Infrastruktur in einem öffentlichen Issue-Tracker.
USER.md(„Keine privaten Infrastrukturdaten") undENVIRONMENT.mdziehen diese Grenzeausdrücklich, der alte Issue-Text zog sie nicht. Was hier bleibt, ist die Schnittstelle;
wo sie läuft, gehört in die private Instanz.
Aufgeteilt in drei Pakete. Als ein
size/Lwäre das nie angefasst worden:typer.Exitsize/Ssize/Msize/MFachlich neu im Text: der Befund zum Iteration Budget Gate (
search/doctorbefreit,lint/typesnicht — ein Subprocess-Server läuft damit nach 60 Aufrufen tot), dieEmpfehlung Direktimport statt Subprocess mit Begründung, der Commit-Stempel in jeder
Antwort als Mittel gegen stille Staleness, und die Feststellung, dass Session-Scoping für
die Leseoberfläche das falsche Instrument ist (Rate Limit statt Budget-Gate).
Relabel
size/L→size/M: die offenen Designfragen, die das L begründet haben(Subprocess vs. Direktimport, Sync-Mechanismus, Session-Scoping), sind beantwortet, und
der Schreibpfad ist nach #32 abgeflossen. Was übrig bleibt, ist mehrere Dateien plus
eigener Testaufwand.
prio/3bleibt, mit jetzt benanntem Auslöser: ein Konsument, der die Oberflächetatsächlich braucht.
Plattformentscheidung 2026-09-01: Python bleibt, Go ist vom Tisch
Ausgelöst durch die Frage, ob Python für einen netzexponierten Dienst security-seitig noch
tragfähig ist, und ob
wikitoolvollständig nach Go migriert werden sollte. ZweiMehr-Perspektiven-Debatten mit neun Positionen, alle tragenden Befunde in der Sitzung selbst
nachgemessen. Ergebnis: Python, kein Go, auch nicht als Option.
Die Begründung, kurz — damit niemand die Frage in sechs Monaten neu aufmacht:
Sicherheit trägt die Migration nicht. Von zehn Befunden am Lesepfad waren zwei
sprachabhängig (ReDoS, YAML-Alias-Budget), beide mit wenigen Zeilen in Python behebbar — jetzt
#33. Die teuersten Befunde waren Isolation und Konfiguration, und die sind sprachneutral.
Die drei Argumente, die für Go sprachen, lösen sich am eigenen Vorhaben auf:
Prozessstart. Das stärkste gemessene Go-Argument verschwindet durch die Entscheidung, die in
diesem Issue ohnehin gefallen ist.
CSafeLoaderist verfügbar und dreimal schneller(246 → 82 ms), plus Cache — #33.
Embedding-Schicht. Lokale Embedding-Erzeugung gibt es in reinem Go nicht; der Weg führt über
ONNX oder llama.cpp, also cgo plus Shared Library plus Cross-Toolchain pro Zielplattform.
Ausgerechnet dort ist Python durch vorkompilierte Wheels besser aufgestellt. Go läuft an dieser
Stelle gegen sein eigenes bestes Argument.
Das Vorbild widerlegt die These.
qmdist entgegenkb/entities/tools/qmd.mdnicht Go oderRust, sondern TypeScript/Bun (verifiziert gegen das Repository): SQLite FTS5 + sqlite-vec +
node-llama-cpp. Das Werkzeug, dessen Fähigkeit hier gewünscht ist, erreicht sie aus einer
Skriptsprache heraus über native Bibliotheken — dasselbe Muster, das Chemenu mit
ripgrepseitTag eins praktiziert. Die Korrektur der Seite läuft separat über
wiki-manage.Der Terminkonflikt gab den Ausschlag.
instructions/private-instance.mdbeziehtStack-Updates per
git merge upstream/mainunter dem Leitsatz „Take the machinery, never thecontent". Eine Portierung ersetzt genau diese Maschinerie; jeder künftige Update-Merge in die
private Instanz aus #30 wäre ein Totalkonflikt in
tools/, und die Auflösungsregel „Konflikt intools/? Nimm die Upstream-Seite" hieße: nimm eine Sprache, für die dein Checkout keine Laufzeithat. Die private Instanz entsteht jetzt.
Zur Vollständigkeit, weil es fair ist: eine agentengeführte Portierung wäre ausführbar
gewesen — geschätzt 25–40 Sitzungen, differentielle Testbank gegen die bestehende Implementierung
als Orakel, mit der Regel „das Go-Binary ist bis zum letzten Schnitt nur Diff-Partner", die den
Zustand zweier halbportierter Implementierungen strukturell ausschließt. Die Entscheidung fällt
also nicht gegen die Machbarkeit, sondern gegen den Nutzen.
Authentifizierung: Traefik-Middleware
Ebenfalls entschieden. Auth wird als Traefik-Middleware vor dem Dienst abgebildet; nicht sauber
authentifizierte Zugriffe erreichen den Python-Prozess nicht. Konsequenzen, im Text
eingearbeitet:
gegen einen authentifizierten Konsumenten, der sich selbst versehentlich beschädigt.
Relabel
prio/3→prio/2Der Auslöser, den ich beim letzten Neuschnitt als Bedingung notiert hatte („ein Konsument, der
die Oberfläche tatsächlich braucht"), ist eingetreten: der Server ist beschlossen, mehrere
Konsumenten sind absehbar. Damit ist es geplante Arbeit, kein wartender Vorschlag mehr.
Teil der Sequenz in #36 (Master: Weg zum MCP-Leseserver) — dort Schritt 3 von 3, nach #33 und
#31. Das Master-Issue hält die getroffenen Entscheidungen, die Messungen und die verifizierten
Befunde gebündelt, damit sie nicht über mehrere Kommentarverläufe verstreut nachgelesen werden
müssen.
Umgesetzt in 2.4.0.
tools/chemenu/mcp/.Die beiden offenen Fragen sind entschieden (Betreiber, 2026-09-02):
stdiozum Entwickeln und Testen ohne Netz,streamable-httpfür dieAuslieferung — der einzige, vor den sich die Traefik-Middleware setzen kann, weil sie ein
HTTP-Reverse-Proxy ist.
sseist über das SDK erreichbar und wird nicht angeboten: derabgelöste Remote-Transport, jetzt darauf zu bauen verschiebt den Wechsel nur.
Testsuite; getrennt würde aus einem Contract eine Versionsabsprache.
Oberfläche:
search,types,describe_type,lint,status.statusist wiebeschrieben serverseitig komponiert und kein Wrapper.
Akzeptanzkriterien:
chemenu.apiimportiert etwasunter
chemenu.commands; ein Test importiert das Servermodul in einem frischen Interpreterund prüft
sys.modules.kb/,reports/oder in git schreibt: alle fünf Toolswerden aufgerufen, danach Dateibaum (Größe und Inhalt),
HEADundgit status --porcelaingegen vorher verglichen; zusätzlich, dassreports/gar nichterst entsteht.
--json-Formen der CLI, Golden-Test hält beide zusammen. Er ruftwikitoolals Subprozess gegen denselben Baum auf, überCHEMENU_ROOT— womit er nebenbeidie Root-Auflösung von außen mitprüft. Für
searchund für beidetypes-Formen.commitundas_of.WIKI_TRACE_DIRzeigt aus dem Arbeitsbaum heraus — erzwungen: der Server verweigertden Start, wenn Telemetrie an ist und in den bedienten Checkout schreiben würde, statt
still umzuleiten. Heute emittiert auf diesem Pfad nichts (der Emitter hängt an
cli.main()und den Gates), die Sperre ist gegen später.
(
git fetch && git reset --hard origin/main) ininstructions/mcp-read-server.md.reset --hardist dort tragend — ein abgedrifteter Baum antwortet zwar richtig, parst aberbei jeder Anfrage neu und stempelt jede Antwort mit
null.Was nicht Teil davon war und offen bleibt: die Middleware selbst steht
(https://gitea.nehmer.net/torben/gitea-mcp-forward-auth), aber ein Aufruf ohne gültige
Credentials wurde gegen diesen Server noch nicht nachweislich abgewiesen — dafür braucht es
ein Deployment. Das hängt am Container-Image, das jetzt #37 ist.
Zum Budget-Gate: wie im Issue argumentiert nicht im Server. Ressourcenschutz im Prozess sind
die Limits aus #33 (Subprozess-Timeout, Regex, YAML-Grenzen).
Befund 8 aus #36 (
lintliestraw/-Dateinamen,lint.py:230) war hier zu entscheiden:lintist unverändert exponiert, inklusiveuncovered_raw_files. Für diese Instanz ist dasfolgenlos — ein Konsument sieht die Dateinamen eines öffentlichen Korpus. Es wird relevant, sobald
mehrere Einreicher existieren, und das ist #32; dort gehört die Entscheidung hin, nicht hierher,
wo sie ohne den Schreibpfad nichts zu schützen hätte.
Gemessen und gefahren: beide Transports gegen den echten Korpus (176 Seiten, korrekter
HEAD-Stempel);
wikitool searchend-to-end von 593 ms auf 347 ms (aus #33), die verbleibenden~262 ms Modulimport entfallen im residenten Prozess.
Menschendoku:
INSTALL-MCP.md. Betrieb:instructions/mcp-read-server.md.