From 62807b91a17dbe572173cb1bc9bcf9dadb2d2f63 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Wed, 2 Sep 2026 09:39:06 +0200 Subject: [PATCH] =?UTF-8?q?docs(rfc):=20MCP-Server=20=E2=80=94=20zweite=20?= =?UTF-8?q?Runde:=20Urheber=20im=20Spiegel-Commit,=20Streamable=20HTTP=20i?= =?UTF-8?q?m=20Backend=20per=20GraalJS=20(Spike=20vorbehalten),=20Bearer-T?= =?UTF-8?q?oken,=20nur=20Dokumente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der erste Entwurf nahm an, MCP erzwinge Node; tatsächlich erzwang es nur der Parser. Das Backend darf den HTTP-Transport tragen, wenn es die JS-Module per GraalJS ausführt (dieselbe Technik wie #idea.drift.js) — Kotlin ist Transport und Zugang, die Logik bleibt einmal vorhanden; Rückfall ist der Node-Dienst hinter Apache. Urheber im Git-Spiegel: Historie im Rumpf plus Trailer statt git --author, damit blame nicht lügt. Entscheidungen in RFC §11 und D93 (zweite Runde). Co-Authored-By: Claude Fable 5 --- docs/DECISIONS.md | 36 ++++++- docs/examples/werkbaum.werkbaum | 21 ++-- docs/rfc/001-mcp-server.md | 177 +++++++++++++++++++++++++++++--- 3 files changed, 207 insertions(+), 27 deletions(-) diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 10a751a..ae6c989 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -8734,7 +8734,37 @@ der Option „zunächst offen halten“ — keine wurde gewählt): - **Die Baum-Serialisierung bleibt im MCP-Paket**, **verteilt wird nur aus dem Repo** (`.mcp.json`, kein npm). -Offen bleibt der Transport über Streamable HTTP (nach `#col.live.owner`) -und die Form der Urheber-Angabe im `pull-doc`-Commit. Die -Umsetzungsreihenfolge steht im RFC; gebaut ist nichts. +**Zweite Runde** (Nutzer: Architektur-Entscheidungen jetzt diskutieren, +auch wenn sie erst spät gebaut werden — RFC §5.7 und §5.10): + +- **Urheber im Spiegel-Commit: Historie im Rumpf plus Trailer.** Ein + Spiegel-Commit trägt den ganzen Serverstand, auch die Zeilen der + Menschen seit dem letzten Commit; den Agenten zum Git-Autor zu machen + ließe `blame` lügen. Git-Autor bleibt der Betreiber, der Rumpf listet + aus der Server-Historie, wer welche Version geändert hat, je + Beteiligtem ein Trailer `Werkbaum-Changed-By:`, dazu `Werkbaum-Version:` + als Untergrenze für den nächsten Lauf. Das gilt für den Cron-Commit + (D88) gleich mit — auch der wird damit ehrlicher. +- **Streamable HTTP läuft im Backend, das die JS-Module per GraalJS + ausführt** — Spike vorbehalten. Die Frage des Nutzers („Kotlin kann das + nicht?“) hat den ersten Entwurf korrigiert: MCP braucht kein Node, nur + der Parser erzwang es, und den kann die JVM ausführen statt + nachzubauen — dieselbe Technik, die der Plan für das IDE-Plugin + vorsieht (`#idea.drift.js`). Kotlin ist Transport und Zugang, die Logik + bleibt einmal vorhanden, kein zweiter Dienst auf dem knappen Host. Der + Spike (ESM in GraalJS, Speicher auf der Zielumgebung) entscheidet vor + Phase 6; Rückfall ist der Node-Dienst hinter Apache, ohne Umbau der + Tool-Schicht, die deshalb von Anfang an als reine ES-Module ohne + Node-APIs geschnitten wird. +- **Zugang über ein statisches Bearer-Token** je Installation + (`WERKBAUM_MCP_TOKEN`, wie das Master-Passwort D77); OAuth 2.1, das die + MCP-Spezifikation vorsieht, erst mit einem Identity Provider. Das Token + ist zugleich die Schicht, über die später das Owner-Passwort + (`#col.live.owner`) kommt — nie als Tool-Parameter. +- **Der entfernte Server bietet nur Dokumente an** — keine Datei-Ablage, + keinen Git-Spiegel; eine echte Teilmenge der lokalen Fassung mit + denselben Verben. + +Offen bleiben nur noch Messungen (GraalJS-Spike, SSE durch den Apache) +und die Prompts. Die Umsetzungsreihenfolge steht im RFC; gebaut ist nichts. diff --git a/docs/examples/werkbaum.werkbaum b/docs/examples/werkbaum.werkbaum index 2157fd2..f03d273 100644 --- a/docs/examples/werkbaum.werkbaum +++ b/docs/examples/werkbaum.werkbaum @@ -233,8 +233,8 @@ - [ ] #ai.mcp.guard: Guard rails: no broken notation, no [^] from an agent (S) - [ ] #ai.mcp.verbs: Node verbs: add, move, size, remove — the rules live in the frontend (M) :#ai.mcp.write - [ ] #ai.mcp.mirror: The shared document mirrors into git after every agent write (S) :#col.git.pull - + [?] #ai.mcp.http: A remote transport over streamable HTTP (M) - - [-] #ai.mcp.kotlin: A server inside the backend (L) %% would need a Kotlin parser, D14 + + [?] #ai.mcp.http: Streamable HTTP, served by the backend running the JS modules (M) :#col.live.owner %% GraalJS spike first + - [-] #ai.mcp.kotlin: A server inside the backend with its own Kotlin parser (L) %% the second grammar D14 forbids --- #wb @@ -1347,12 +1347,15 @@ #ai.mcp.http Streamable HTTP as a second transport, so a hosted agent can reach the - server without a local process. Optional: stdio covers the local agents - first, and remote access raises the authentication question the backend - has not answered yet (#col.live.owner). + server without a local process. Served by the Spring backend, which runs + the same JS modules through GraalJS — Kotlin is transport and access + (a static bearer token via Spring Security), the logic stays in one place. + A spike must first prove the modules run there and what they cost in + memory; if it fails, the Node package serves HTTP as its own service. + Documents only: no file store, no git mirror over the network. #ai.mcp.kotlin - Building the server into the Spring backend would put it next to the - documents — but every useful tool needs the parser, and a Kotlin parser is - exactly the second grammar D14 forbids. Discarded in favour of the Node - package. + Building the server into the Spring backend with a parser of its own would + put it next to the documents — but a Kotlin parser is exactly the second + grammar D14 forbids. Discarded; the backend may host the transport + (#ai.mcp.http), never a second grammar. diff --git a/docs/rfc/001-mcp-server.md b/docs/rfc/001-mcp-server.md index 2a6080b..b500cd2 100644 --- a/docs/rfc/001-mcp-server.md +++ b/docs/rfc/001-mcp-server.md @@ -6,7 +6,7 @@ | Plan-Knoten | `#ai.mcp` in `docs/examples/werkbaum.werkbaum` | | Entscheidung | D93 in `docs/DECISIONS.md` (Verweis auf dieses RFC) | | Berührt | `frontend/src/*` (headless-Module, lesend), neues Paket `mcp/`, `tools/pull-doc` (Aufrufer), README, `.mcp.json` | -| Berührt nicht | Notation (SPEC), `llms.md`, Backend-Code, Deploy-Skripte | +| Berührt nicht | Notation (SPEC), `llms.md`, Deploy-Skripte; Backend-Code erst in Phase 6 (§7.3) | ## 1. Zusammenfassung @@ -298,6 +298,36 @@ setzt genau darauf auf: Im Plan steht das als `#ai.mcp.mirror` mit Abhängigkeit auf `#col.git.pull`. +**Wer steht im Commit?** Ein Spiegel-Commit enthält den **ganzen** +Serverstand — die Zeilen des Agenten und alles, was Menschen seit dem +letzten Commit geändert haben. „Der Agent hat diesen Commit gemacht“ ist +also nur die halbe Wahrheit, und die Form der Urheber-Angabe entscheidet, +ob `git blame` später lügt. Vier Formen, mit dem, was sie aussagen: + +| Form | Aussage | Preis | +|---|---|---| +| **a) `git commit --author="Claude Code <…>"`** | Der Agent ist Git-**Autor** des Commits — `blame` schreibt ihm **jede** Zeile darin zu, auch die, die Anna getippt hat | Falsche Blame-Auskunft; und Git verlangt eine E-Mail-Adresse, die es nicht gibt | +| **b) `Co-Authored-By:`-Trailer** | Die Konvention, die das Repo für Claude-Commits schon nutzt: „hat beigetragen“, ohne Autor zu sein; GitHub zeigt Co-Autoren an | Braucht ebenfalls `Name `; für Menschen aus der Server-Historie gibt es nur den Anzeigenamen | +| **c) Freitext im Betreff** (`… (Version 8, via Claude Code)`) | Lesbar im `git log --oneline`, nennt den Auslöser | Nicht maschinenlesbar; nennt nur den Auslöser, nicht die Beteiligten | +| **d) Historie im Rumpf + eigene Trailer** | Der Rumpf listet aus `GET /history`, **wer welche Version** seit dem letzten Spiegel-Commit geändert hat („v7 Anna · v8 Claude Code“); je Beteiligtem ein Trailer `Werkbaum-Changed-By: `, dazu `Werkbaum-Version: 8` | Ein zweiter `GET` je Commit; eigene Trailer statt der GitHub-Konvention | + +**Vorschlag: d, mit c im Betreff.** Es ist die einzige Form, die die +Wahrheit des Commits trägt: Git-Autor bleibt, wer den Prozess betreibt +(das ist buchstäblich richtig — er hat committet), die Beteiligten stehen +mit Namen im Rumpf, maschinenlesbar per `git interpret-trailers`, und der +`Werkbaum-Version:`-Trailer gibt `pull-doc` beim nächsten Lauf die +Untergrenze für „seit dem letzten Commit“ — ohne ihn müsste der Betreff +geparst werden. Damit wird der **Cron-Commit** (D88) im selben Zug +ehrlicher: Auch dort stehen dann die Menschen, die seither geändert haben. +Der Schalter dafür (`--with-history`) kommt ins Skript, nicht in den +Aufrufer (D77-Nachtrag); der MCP-Server setzt ihn immer. Die Anzeigenamen +bleiben, was sie in der Server-Historie sind: Behauptungen, keine +Nachweise (D86) — im Commit steht deshalb „Changed-By“, nicht „Author“. + +Verworfen: **a** (Blame lügt) und **b** allein (erfundene E-Mail-Adressen, +und ein Trailer ohne Versionsbezug sagt nicht, *was* der Beitrag war). +Entscheidung: §11. + ### 5.8 Prompts (optional, Phase 6) Zwei vorgefertigte Anweisungen, die ein Host dem Nutzer anbietet: @@ -325,6 +355,89 @@ node mcp/server.js [--root ]… [--server ] [--name ]`: statt stdio ein Streamable-HTTP-Endpunkt (§5.10); + schließt `--root` und `--mirror` aus. + +### 5.10 Streamable HTTP — der entfernte Transport + +Für lokale Hosts genügt stdio: Der Host startet den Server als +Kindprozess, der Prozess hat die Rechte des Nutzers, die Dateiwurzeln sind +seine. Ein **gehosteter** Agent (Anthropic-API-MCP-Connector, Claude +Desktop mit entferntem Server, ein Managed Agent) kann keinen Kindprozess +starten; er braucht eine URL. Das ist `#ai.mcp.http`, und drei Fragen +hängen daran — Ort, Zugang, Umfang. Sie sind Architektur und werden jetzt +entschieden, gebaut wird die Phase erst, wenn ein solcher Host wirklich +ansteht. + +**Ort — wo läuft der HTTP-Server?** Vorweg, weil die Frage kam: MCP +verlangt **kein** Node. Das Protokoll ist JSON-RPC über stdio oder HTTP, +und es gibt offizielle SDKs für TypeScript, Python, **Kotlin und Java** +sowie einen Spring-AI-Starter für MCP-Server. Das Backend *kann* also einen +MCP-Server anbieten. Was Node im ersten Entwurf erzwang, war nicht das +Protokoll, sondern der **Parser**: Jedes nützliche Tool braucht +`parser.js`/`model.js`, und D14 verbietet eine zweite Grammatik. Daraus +folgen drei echte Optionen: + +| Option | Aussage | Preis | +|---|---|---| +| **a) Dasselbe Node-Paket als eigener Dienst** auf dem Server-Host: systemd-User-Unit wie das Backend (D77), lauscht auf `127.0.0.1`, Apache reicht `/mcp/` per `RewriteRule … [P]` durch — dieselbe Zeile wie für `/api/` | Ein Paket, zwei Transporte; die Infrastruktur aus D77 wird wiederverwendet; das Paket läuft **neben** dem Backend und spricht es über `127.0.0.1` an | Node auf dem Server (bisher nur JDK, D77 — per nvm ins Home, dasselbe Muster wie das JDK); ein zweiter Dienst zu betreiben (`remote mcp …` als Ziel); das Bearer-Token prüft der Node-Dienst selbst | +| b1) Im Kotlin-Backend, mit eigenem Parser | ein Dienst | **D14 — verworfen** (`#ai.mcp.kotlin`) | +| **b2) Im Kotlin-Backend, das die JS-Module selbst ausführt** — GraalJS (`org.graalvm.polyglot`) lädt `frontend/src/*.js` als ES-Module in der JVM; Kotlin ist nur Transport (offizielles Kotlin-SDK oder Spring-AI-Starter) und Zugang (Spring Security, das es schon gibt) | **Ein** Deployment, kein Node auf dem Server, kein zweiter Dienst; das Bearer-Token ist ein Spring-Security-Filter neben dem Master-Passwort; die Werkbaum-Logik bleibt **einmal** vorhanden — dieselbe Technik, die der Plan für das IDE-Plugin vorsieht (`#idea.drift.js`: „den einen JS-Parser im IDE laufen lassen“) | Zwei neue Backend-Abhängigkeiten (MCP-SDK, GraalJS); Speicher auf dem knappen Host (D76-Nachtrag 3 — GraalJS im Interpreter-Modus auf Stock-OpenJDK kostet nach Erfahrungswerten einige zehn MB, **zu messen**); ein Spike, der beweist, dass die ESM-Module dort laufen (`crypto.subtle` in `live.js` gibt es in GraalJS nicht — die Prüfsumme muss dort aus Kotlin kommen); und die Tool-Schicht muss so geschnitten sein, dass **dieselbe** JS-Datei in Node (stdio) und in GraalJS (HTTP) läuft | +| c) Gar nicht; entfernte Hosts nutzen eine stdio-Brücke (`mcp-remote` o. Ä.) | nichts zu bauen | Löst das Problem nicht: Die Brücke braucht selbst einen Prozess beim Nutzer, und ein Managed Agent hat keinen | + +Für die **lokalen** Agenten (Claude Code mit Dateien und Git-Spiegel) +bleibt stdio in jedem Fall Node — b2 ersetzt nicht das Paket, sondern nur +den entfernten Transport. Damit die beiden Hosts nicht auseinanderlaufen, +wird die Tool-Schicht (`inspect`, `guard`, `verbs`) als reine ESM-Datei +ohne Node-APIs geschnitten und von beiden Seiten aufgerufen; Ablagen und +Transport sind je Host eigen. Das ist bei b2 Pflicht, bei a nur Ordnung — +und es kostet nichts, es von Anfang an so zu tun. + +**Vorschlag: b2, unter Vorbehalt eines Spikes; a als Rückfall.** b2 ist +die bessere Architektur — ein Dienst, die vorhandene Zugangsschicht, kein +zweites Laufzeitsystem auf dem Server —, hängt aber an zwei Messungen, die +vor Phase 6 stehen: Laufen die Module in GraalJS (ESM-Laden, kein +`crypto.subtle`, kein `TextEncoder`-Unterschied), und was kostet es an +Speicher auf der Zielumgebung. Fällt eine der beiden durch, ist a ohne +Umbau der Tool-Schicht möglich. Unabhängig von der Wahl gilt: Streamable +HTTP läuft im **zustandslosen** Modus (keine Sitzungs-Ids, die einen +Neustart nicht überleben), und ob der Apache der Zielumgebung den +SSE-Strom langer Werkzeugaufrufe **ungepuffert** durchreicht, ist zu +messen (die D17-Nachtrag-4-Lehre) — Long Polling ist gemessen +(D76-Nachtrag 2), ein Strom nicht. + +**Zugang — wer darf?** Die MCP-Spezifikation sieht für HTTP-Transporte +OAuth 2.1 vor (der Server als Resource Server, Discovery über +Protected-Resource-Metadaten). Werkbaum hat aber weder Nutzerkonten noch +einen Identity Provider; der Zugriff auf Dokumente ist die **unerratbare +UUID** (D76), und die REST-API steht damit heute schon jedem offen, der +eine kennt. + +| Option | Aussage | Preis | +|---|---|---| +| a) **Kein Zugang nötig** — der Server tut über HTTP nichts, was die REST-API nicht auch tut (Dokument per UUID lesen/schreiben, `llms.md` ist öffentlich) | konsequent zum UUID-Modell; nichts zu verwalten | Ein offener Rechen-Endpunkt (`inspect` parst beliebig große Texte) lädt zum Missbrauch ein; und sobald `#col.live.owner` existiert, braucht der Server einen Weg, das Owner-Passwort **je Aufrufer** zu bekommen — ohne Zugangsschicht gibt es den nicht | +| **b) Statisches Bearer-Token** je Installation (`WERKBAUM_MCP_TOKEN` in der Umgebung, wie das Master-Passwort D77); Hosts geben es als Header mit (Claude Code: `--header "Authorization: Bearer …"`, der API-MCP-Connector: `authorization_token`) | Eine Zeile Konfiguration, keine Konten; hält den Rechen-Endpunkt zu; das Token reist über HTTPS (Apache), nie im Transkript — es steht in der Host-Konfiguration | Ein Token für alle Aufrufer: keine Unterscheidung, wer schreibt (den Namen liefert weiterhin der Handshake — eine Behauptung); Rotation von Hand | +| c) OAuth 2.1 von Anfang an | spezifikationsgemäß; Hosts wie Claude.ai-Connectoren erwarten es | Braucht einen Authorization Server, den es nicht gibt — entweder selbst bauen (weit über den Anlass hinaus) oder an einen IdP hängen; die Taiga-Instanz wechselt auf OIDC (D91-Nachtrag 1), das wäre der naheliegende, aber fremde Anker | + +**Vorschlag: b jetzt, c dann, wenn es einen IdP gibt.** Das Bearer-Token +ist die Zugangsschicht, die `#col.live.owner` ohnehin braucht: Das +Owner-Passwort eines Dokuments kommt später **nicht** als Tool-Parameter +(Transkript), sondern über dieselbe Umgebung des Dienstes — je Dokument +eine Zuordnung, die der Betreiber pflegt. OAuth 2.1 wird nicht +ausgeschlossen, nur nicht vor dem IdP gebaut; die Schnittstelle der Tools +ändert sich dadurch nicht. + +**Umfang — was bietet der entfernte Server an?** Weniger als der lokale: +**keine Datei-Ablage** (`--root` entfällt — der Dienst hat kein +Nutzer-Dateisystem, und ein Pfad-Parameter über HTTP wäre die Einladung, +die §8 verbietet) und **keinen Git-Spiegel** (`--mirror` entfällt — der +Cron-`pull-doc` auf dem Server-Host übernimmt das Archiv für alle, ob der +Schreibende lokal oder entfernt war). Übrig bleiben Guide, Dokumente und +alle Tools auf Dokumenten. Damit ist die entfernte Fassung eine **echte +Teilmenge** der lokalen, und ein Agent, der beides kennt, merkt keinen +Unterschied in den Verben. + +Entscheidung: §11. Für Claude Code: @@ -412,15 +525,18 @@ mcp/ server.js MCP-Rahmen: Resources, Tools, Prompts registrieren; stdio stores/file.js lesen, Prüfsumme, atomar schreiben, Wurzel-Prüfung stores/live.js GET / PATCH gegen /api/v1/documents (D76), clientId/seq - inspect.js Baum → JSON, Warnungen → {type, line, text}, Pfad → Stationen - guard.js Leitplanken (§5.5): Struktur-Warnungen, [^]-Sperre - verbs.js Knoten-Verben → Aufrufe von frontend/src/edit.js, dann Ablage + core/inspect.js Baum → JSON, Warnungen → {type, line, text}, Pfad → Stationen + core/guard.js Leitplanken (§5.5): Struktur-Warnungen, [^]-Sperre + core/verbs.js Knoten-Verben → Aufrufe von frontend/src/edit.js (Text → Text) tests/*.test.js Vitest gegen einen In-Memory-Transport des SDK; Fixtures = SPEC §10 und der mitgelieferte Plan ``` -Die Regeln in `inspect.js` und `guard.js` sind Text→JSON bzw. Text→Text und -damit headless testbar — die Hausregel aus D54-Nachtrag 3 gilt auch hier; -`server.js` verdrahtet nur. +`core/` ist die **gemeinsame Tool-Schicht**: reine ES-Module ohne +Node-APIs (kein `fs`, kein `process`, keine `crypto`), Text hinein, JSON +oder Text heraus. Genau diese Dateien führt in Phase 6 auch das Backend in +GraalJS aus (§5.10, b2) — Ablagen und Transport sind je Host eigen, die +Regeln nicht. Die Regeln sind damit headless testbar — die Hausregel aus +D54-Nachtrag 3 gilt auch hier; `server.js` verdrahtet nur. ### 7.3 Backend @@ -434,6 +550,18 @@ Dinge sind zu **prüfen**, nicht zu bauen: späterer Filter („nur menschliche Änderungen zeigen“) wäre ein eigener Wunsch. +**Falls §5.10 auf b2 fällt** (HTTP-Transport im Backend), kommt in +Phase 6 dazu — und erst dann: ein Paket `de.werkbaum.integration.mcp` mit +dem MCP-Endpunkt unter `/api/v1/mcp` (Kotlin-SDK oder Spring-AI-Starter), +ein GraalJS-Kontext, der `frontend/src/*.js` und die gemeinsame +Tool-Schicht lädt (die Dateien wandern beim Bauen ins Jar als Ressourcen — +dieselbe Quelle, keine Kopie), die Prüfsumme aus `java.security` +statt `crypto.subtle`, ein Bearer-Token-Filter in `SecurityConfiguration` +neben dem Master-Passwort, `taiga`/`mcp` in `GET /info`, und +`build.gradle.kts` mit den zwei Abhängigkeiten (Rückfragepflicht, +CLAUDE.md — mit §11 gestellt). Der Speicher-Nachweis gehört in den +DECISIONS-Nachtrag, gemessen wie in D77. + Perspektivisch berührt: `#col.live.owner`. Sobald Verwaltungs-Aktionen an ein Owner-Passwort gebunden sind, braucht der MCP-Server einen Weg, es zu übergeben — **nie** als Tool-Parameter (Prozessliste, Transkript), sondern @@ -465,7 +593,7 @@ eingecheckt, wie im Frontend). | `README.md` / `README.de.md` | Abschnitt „Für KI-Agenten: der MCP-Server“ — Installation, die drei Quellen, die Leitplanken | | `docs/CHANGELOG.md` | je Phase eine Zeile | | `.github/workflows/*` | `npm --prefix mcp test` neben dem Frontend-Test; kein Deploy (lokales Werkzeug) | -| `tools/pull-doc` | **unverändert im Verhalten**; ein neuer Schalter `--author ` für den Urheber in der Commit-Nachricht (§5.7) — der Schalter kommt ins Skript, nicht in den Aufrufer (D77-Nachtrag) | +| `tools/pull-doc` | **unverändert im Verhalten**; ein neuer Schalter `--with-history` (§5.7): holt `GET /history`, listet im Rumpf, wer welche Version seit dem letzten Spiegel-Commit geändert hat, und setzt die Trailer `Werkbaum-Changed-By:` und `Werkbaum-Version:` — der Schalter kommt ins Skript, nicht in den Aufrufer (D77-Nachtrag); der MCP-Server setzt ihn immer, der Cron darf | | `scripts/deploy-*.sh`, `tools/remote` | **unverändert** — nichts davon läuft auf dem Server | | `frontend/public/llms.md` | **unverändert** (Notation, D43); ein Verweis auf den Server gehört in `llms.txt` (Wegweiser, D43-Nachtrag 2) | @@ -503,7 +631,7 @@ eingecheckt, wie im Frontend). | Git als **Quelle** (Server folgt dem Repo) | Umkehrung von §5.7; Konfliktmarker mitten in der Notation, eigene Fragen | `#col.git.pr`, `#col.git.auto` | | `?sourceUrl=`-Quellen | nur lesend; der Agent kann die URL selbst holen | Nachtrag bei Bedarf | | Streamable HTTP | Phase 6, hängt an der Authentifizierungsfrage | `#ai.mcp.http` | -| Ein Kotlin-Server | D14 | `#ai.mcp.kotlin` (`[-]`) | +| Ein Kotlin-Server **mit eigenem Parser** | D14 — das Backend darf den Transport tragen (§5.10, GraalJS), nie eine zweite Grammatik | `#ai.mcp.kotlin` (`[-]`) | ## 10. Umsetzungsreihenfolge @@ -539,7 +667,14 @@ Plan-Knoten gehen beim Mergen auf `[x]`. verschmutzte Spiegel-Datei wird vom nächsten Commit überschrieben und die Abweichung steht im Diff. 6. **Prompts und HTTP** (`#ai.mcp.http`, optional): die zwei Prompts; - Streamable HTTP erst nach `#col.live.owner`. + Streamable HTTP erst nach `#col.live.owner`, und davor die zwei + Messungen aus §5.10 — der GraalJS-Spike (laufen `parser.js`, + `model.js` und `mcp/core/*` als ES-Module in der JVM, was kostet der + Kontext auf der Zielumgebung) und der SSE-Strom durch den Apache. Fällt + der Spike durch, trägt das Node-Paket den Transport (`--http`) als + eigener Dienst. Nachweis: derselbe Werkzeugaufruf liefert über stdio + und über HTTP dasselbe JSON; ohne Bearer-Token 401; ein `--root`-Pfad + wird über HTTP gar nicht erst angeboten. ## 11. Entscheidungen (Multiple-Choice-Runde, 2026-09-02) @@ -553,10 +688,22 @@ Plan-Knoten gehen beim Mergen auf `[x]`. | Schreib-Tools (§5.4) | **Zusätzlich Knoten-Verben** | `add_node`, `move_node`, `set_size`, `remove_node`; Regeln in `frontend/src/edit.js`, nicht im Paket | | Verteilung | **Nur aus dem Repo** | `.mcp.json`; npm erst auf Nachfrage von außen | +**Zweite Runde** (Nutzer: Architektur-Entscheidungen jetzt diskutieren, +auch wenn sie erst später gebaut werden — §5.7 und §5.10): + +| Frage | Entschieden | Anmerkung | +|---|---|---| +| Urheber im Spiegel-Commit (§5.7) | **Historie im Rumpf + Trailer** | `Werkbaum-Changed-By:` je Beteiligtem, `Werkbaum-Version:`; Git-Autor bleibt der Betreiber; gilt auch für den Cron-Commit (`pull-doc --with-history`) | +| HTTP: Ort (§5.10) | **Im Backend, das die JS-Module per GraalJS ausführt** — Spike vorbehalten | Kotlin ist Transport und Zugang, die Logik bleibt einmal vorhanden; fällt der Spike durch (ESM in GraalJS, Speicher auf dem Host), Rückfall auf den Node-Dienst hinter Apache ohne Umbau der Tool-Schicht | +| HTTP: Zugang (§5.10) | **Statisches Bearer-Token** | `WERKBAUM_MCP_TOKEN`; OAuth 2.1 erst mit einem IdP | +| HTTP: Umfang (§5.10) | **Nur Dokumente** | keine Datei-Ablage, kein Git-Spiegel über HTTP; echte Teilmenge der lokalen Fassung | + **Weiterhin offen** (stellt sich erst beim Bauen oder danach): -1. **Streamable HTTP** — wartet auf `#col.live.owner`; bis dahin stdio. -2. **`--author`-Schalter in `pull-doc`** — Form der Urheber-Angabe in der - Commit-Nachricht (Trailer `Co-Authored-By:` oder Freitext im Betreff); - entscheidet sich beim Bauen von Phase 5. -3. **Prompts** (§5.8) — ob zwei genügen und wie sie heißen; Phase 6. +1. **Prompts** (§5.8) — ob zwei genügen und wie sie heißen; Phase 6. +2. **Der GraalJS-Spike** — laufen `parser.js`/`model.js` und die + Tool-Schicht als ES-Module in der JVM, und was kostet der Kontext an + Speicher auf der Zielumgebung (D76-Nachtrag 3)? Messung vor Phase 6; + das Ergebnis entscheidet zwischen b2 und a (§5.10) und gehört als + Nachtrag zu D93. +3. **SSE durch den Apache** — Messung vor Phase 6 (§5.10).