stack: MCP submit-Tool mit Upload Review Gate und Quarantäne-Schreibpfad (schliesst #32)
Files changed: - .gitignore - AGENTS.md - CHANGES.md - INSTALL-MCP.md - README.md - VERSION - docs/why-gates-are-code.md - instructions/gates.md - instructions/ingest-queue.md - instructions/mcp-read-server.md - instructions/wiki-ingest/SKILL.md - raw/CONTRACT.md - tools/CONTRACT.md - tools/chemenu/cli.py - tools/chemenu/commands/docs_verify.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/upload_cmd.py - tools/chemenu/config.py - tools/chemenu/mcp/server.py - tools/chemenu/tests/test_doctor.py - tools/chemenu/tests/test_mcp_server.py - tools/chemenu/tests/test_upload.py - tools/chemenu/tests/test_upload_cmd.py - tools/chemenu/upload.py
This commit is contained in:
+51
-9
@@ -11,10 +11,12 @@ und Server rufen dieselben Funktionen auf; ein Golden-Test hält ihre Ausgaben g
|
||||
Was `tools/wikitool search --json` liefert, liefert das MCP-Tool `search` auch — plus den
|
||||
Commit, aus dem die Antwort berechnet wurde.
|
||||
|
||||
**Was er nicht ist.** Kein Schreibpfad. Es gibt kein Tool, das eine Seite anlegt, ändert oder
|
||||
publiziert — nicht weil eine Liste gefiltert wird, sondern weil der Server nichts unter
|
||||
`tools/chemenu/commands/` importiert. Die Funktionen sind aus diesem Prozess heraus nicht
|
||||
erreichbar.
|
||||
**Was er nicht ist.** Kein Schreibpfad nach `kb/`. Es gibt kein Tool, das eine Seite anlegt,
|
||||
ändert oder publiziert — nicht weil eine Liste gefiltert wird, sondern weil der Server nichts
|
||||
unter `tools/chemenu/commands/` importiert. Die Funktionen sind aus diesem Prozess heraus nicht
|
||||
erreichbar. Optional gibt es ein sechstes Tool, `submit` (Schritt 7): es schreibt, aber nur in
|
||||
eine Quarantäne, die kein anderer Befehl liest — eine Positiv-Liste im Code statt einer
|
||||
Abwesenheit, und ein Mensch entscheidet über jede Beförderung daraus.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
@@ -87,7 +89,7 @@ Arbeitsverzeichnis erbt:
|
||||
Checkout, in dem das Paket selbst liegt — für eine einzelne Instanz reicht das, aber wer mehrere
|
||||
Korpora hat, setzt sie besser immer.
|
||||
|
||||
Danach kennt der Client fünf Werkzeuge:
|
||||
Danach kennt der Client fünf Werkzeuge, und optional ein sechstes:
|
||||
|
||||
| Tool | Was es beantwortet |
|
||||
|---|---|
|
||||
@@ -96,6 +98,7 @@ Danach kennt der Client fünf Werkzeuge:
|
||||
| `describe_type` | Der vollständige Vertrag eines Typs: Felder, Pflichtangaben, Enums |
|
||||
| `lint` | Strukturelle Befunde: kaputte Wikilinks, Waisen, Index-Drift, Schema-Lücken |
|
||||
| `status` | Momentaufnahme: Seitenzahl, Verteilung auf Collections, Befundzahlen |
|
||||
| `submit` *(optional, Schritt 7)* | Reicht ein Dokument in die Prüf-Warteschlange ein — kein Schreibpfad nach `kb/`, nur in eine Quarantäne |
|
||||
|
||||
## Schritt 4: Ausgeliefert starten (streamable HTTP)
|
||||
|
||||
@@ -143,6 +146,12 @@ Nicht in den Iteration Budget Gate: der begrenzt eine *Agenten-Session* am unbem
|
||||
über den Wiki-Zustand, weshalb Retrieval von ihm ausgenommen ist. Ihn als Rate Limiter zu
|
||||
benutzen würde ihn dazu verwässern.
|
||||
|
||||
**Ist der `submit`-Pfad scharf geschaltet (Schritt 7), kommt eine zweite Pflicht hinzu:** die
|
||||
Middleware muss den konfigurierten Identitäts-Header (Default `X-Forwarded-User`) selbst setzen
|
||||
und eine vom Client mitgeschickte Kopie verwerfen. Der Prozess vertraut diesem Header als Wert
|
||||
für `submitter` — ein Header, den der Client selbst setzen dürfte, wäre keine Identität, sondern
|
||||
eine Behauptung.
|
||||
|
||||
## Schritt 6: Den Korpus aktuell halten
|
||||
|
||||
Der Server liest den Arbeitsbaum. Ein veralteter Checkout antwortet selbstbewusst falsch —
|
||||
@@ -167,6 +176,34 @@ Parse wieder, solange der Commit gleich bleibt, und cacht einen **schmutzigen Ba
|
||||
nicht**. Ein abgedrifteter Checkout antwortet also zwar richtig, parst aber bei jeder Anfrage
|
||||
neu — und stempelt jede Antwort mit `"commit": null`, weil sie keiner Revision entspricht.
|
||||
|
||||
## Schritt 7: Optional - den `submit`-Pfad freischalten
|
||||
|
||||
Ohne diesen Schritt existiert `submit` als Tool nicht — nicht ungenutzt, sondern nicht
|
||||
registriert. Die Datei `.wikitool-upload.json` im bedienten Korpus schaltet ihn frei:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"identity_header": "X-Forwarded-User",
|
||||
"max_bytes": 10485760,
|
||||
"allowed_extensions": [".md", ".txt", ".pdf", ".html", ".csv", ".json", ".png", ".jpg"],
|
||||
"quota": { "submissions_per_day": 20, "bytes_per_day": 52428800 }
|
||||
}
|
||||
```
|
||||
|
||||
Jedes Feld ist Pflicht, keines hat einen eingebauten Default außer `identity_header` — eine
|
||||
fehlerhafte Datei ist ein Startfehler des Servers, kein „keine Beschränkung": das Ziel ist
|
||||
absichtlich die sichere Richtung. `identity_header` muss der Header sein, den Schritt 5 oben
|
||||
gerade eben *scharf gemacht* hat (Middleware setzt, Client-Kopie verworfen) — sonst wird jede
|
||||
Einreichung mangels Identität abgelehnt.
|
||||
|
||||
Eingereichte Dateien landen in `mcp-upload/<id>/`, gitignored, von keinem anderen Kommando
|
||||
gelesen. Ein Mensch prüft und befördert sie über `wikitool upload accept <id> --confirm <token>`
|
||||
(Exit 42 beim ersten Versuch, mit Manifest und Token in der Ausgabe) oder verwirft sie über
|
||||
`wikitool upload reject <id> --reason "<warum>"` — siehe
|
||||
[instructions/ingest-queue.md](instructions/ingest-queue.md) für den Prüfablauf. Beide Kommandos
|
||||
laufen im selben Checkout wie der Server, nicht im Prozess selbst.
|
||||
|
||||
## Verifikation
|
||||
|
||||
Läuft es? Der schnellste Test ohne Client — startet den Server über stdio, listet die Tools und
|
||||
@@ -219,9 +256,11 @@ anderes Python als das der Instanz. Im Client den absoluten Pfad auf `tools/.ven
|
||||
setzen.
|
||||
|
||||
**`"commit": null` in jeder Antwort** — der bediente Baum hat uncommittete Änderungen. Entweder
|
||||
läuft der Sync nicht, oder etwas schreibt in den Korpus, das dort nichts zu suchen hat. Der
|
||||
Server selbst schreibt nie; ein Test prüft das, indem er alle fünf Tools aufruft und Dateibaum,
|
||||
`HEAD` und `git status --porcelain` vorher/nachher vergleicht.
|
||||
läuft der Sync nicht, oder etwas schreibt in den Korpus, das dort nichts zu suchen hat. Die
|
||||
fünf Lesewerkzeuge schreiben nie, und `submit` (falls scharf) ausschließlich nach
|
||||
`mcp-upload/` — gitignored, also selbst kein Grund für `"commit": null`; ein Test prüft das,
|
||||
indem er alle Tools aufruft und Dateibaum, `HEAD` und `git status --porcelain` vorher/nachher
|
||||
vergleicht.
|
||||
|
||||
**`commit` nennt eine alte Revision** — der Sync aus Schritt 6 läuft nicht.
|
||||
|
||||
@@ -243,4 +282,7 @@ Ein **Container-Image** für den Betrieb gibt es noch nicht; es ist als eigenes
|
||||
mitsamt den Entscheidungen, die dafür noch offen sind (Korpus im Image oder als Volume, wer den
|
||||
Sync ausführt, Basis-Image, Healthcheck):
|
||||
<https://gitea.nehmer.net/torben/chemenu/issues/37>. Bis dahin ist der Weg oben — venv,
|
||||
`python -m chemenu.mcp`, Proxy davor — der vollständige.
|
||||
`python -m chemenu.mcp`, Proxy davor — der vollständige. Ist der `submit`-Pfad scharf, gehört
|
||||
`mcp-upload/` zu derselben offenen Frage: es muss denselben Neustart und dieselbe
|
||||
Persistenzentscheidung überleben wie der Rest des Checkouts, sonst verliert eine eingereichte,
|
||||
noch nicht geprüfte Datei ihre Quarantäne.
|
||||
|
||||
Reference in New Issue
Block a user