docs(rfc): MCP-Server nur im Backend, kein Node als Laufzeit — dritte Runde; Hausregel: neue Technologie braucht ausdrückliche Zustimmung

Nutzer-FYI: Node.js einzuführen ist eine Dimension schwerer als eine
Backend-Abhängigkeit und muss dem Entwickler deutlich gemacht werden —
als Regel in CLAUDE.md. Aus den nachgefragten Beweggründen folgt: kein
lokaler Prozess. Der MCP-Server lebt im Backend (Streamable HTTP,
Bearer-Token), führt die Frontend-Module per GraalJS aus, jedes Werkzeug
hat eine text- und eine document-Form; das Node-Paket ist verworfen,
seine Tool-Schicht lebt als frontend/src/inspect.js, guard.js, edit.js.
Backend-Abhängigkeiten GraalJS + Spring-AI-Starter zugestimmt; der
GraalJS-Spike steht vor allem anderen. RFC neu geschrieben (§12 hält die
drei Runden fest), Plan-Teilbaum #ai.mcp neu, D93-Nachtrag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-09-02 10:04:11 +02:00
co-authored by Claude Fable 5
parent 62807b91a1
commit 1f186eddce
5 changed files with 568 additions and 567 deletions
+7
View File
@@ -36,6 +36,13 @@ Integrations-Backend.
`I18N`-Objekt (frontend/index.html) anlegen, dann in alle Sprachen `I18N`-Objekt (frontend/index.html) anlegen, dann in alle Sprachen
übersetzen. übersetzen.
- Keine neuen Laufzeit-Abhängigkeiten ohne Rückfrage (gilt in beiden Teilen). - Keine neuen Laufzeit-Abhängigkeiten ohne Rückfrage (gilt in beiden Teilen).
- **Eine neue Technologie ist eine Dimension schwerer als eine neue
Abhängigkeit** — Node.js als *Laufzeit* (auf dem Server oder als eigenes
Paket; bisher ist es nur Build-Werkzeug), eine weitere Sprache, ein
weiteres Laufzeitsystem. Das wird dem Entwickler **deutlich gemacht** und
seine **Zustimmung eingeholt**, als eigene benannte Entscheidung mit den
Alternativen innerhalb der vorhandenen Technologien (JVM, Vanilla JS im
Browser) — nie als Nebensatz eines Vorschlags (D93-Nachtrag).
- Der Notationstext ist das führende Datenformat; kein Teil erfindet ein - Der Notationstext ist das führende Datenformat; kein Teil erfindet ein
eigenes Speicherformat für die Struktur. eigenes Speicherformat für die Struktur.
- Im mitgelieferten Plan `docs/examples/werkbaum.werkbaum` bekommt **jeder** - Im mitgelieferten Plan `docs/examples/werkbaum.werkbaum` bekommt **jeder**
+1 -1
View File
@@ -19,7 +19,7 @@ reverse.
## 2026-09-02 ## 2026-09-02
- An MCP server for AI agents recorded as planned: a Node package that reuses the one JS parser, hands the plan to Claude Code and other agents as resource and tools, writes changes as conflict-safe line diffs, and mirrors the shared document into git after every agent write — spelled out in an RFC before it is built - An MCP server for AI agents recorded as planned: it lives in the backend, runs the one JS parser through GraalJS, hands plans to Claude Code and other agents as resources and tools over streamable HTTP, and writes changes as conflict-safe line diffs — no new runtime, spelled out in an RFC before it is built
## 2026-08-28 ## 2026-08-28
+54 -2
View File
@@ -8765,6 +8765,58 @@ auch wenn sie erst spät gebaut werden — RFC §5.7 und §5.10):
keinen Git-Spiegel; eine echte Teilmenge der lokalen Fassung mit keinen Git-Spiegel; eine echte Teilmenge der lokalen Fassung mit
denselben Verben. denselben Verben.
Offen bleiben nur noch Messungen (GraalJS-Spike, SSE durch den Apache) **Dritte Runde — Node ist eine Technologie, keine Abhängigkeit; der
und die Prompts. Die Umsetzungsreihenfolge steht im RFC; gebaut ist nichts. Entwurf kippt (2026-09-02, später am Tag).** Nutzer-FYI, als wichtig
notiert: *„Bevor komplette neue Technologien eingeführt werden, hier z. B.
Node.js, sollte dies dem menschlichen Entwickler deutlich gemacht werden
und dessen Zustimmung eingeholt werden. Node.js einzuführen ist eine
Dimension schlimmer als eine neue Dependency für den Kotlin/Spring-Boot
Backend."* Das RFC hatte Node — bisher im Repo reines Build-Werkzeug
(Vite) — als **Laufzeit** vorgeschlagen, lokal als Paket und auf dem
Server als Dienst, und dabei nur die SDK-Abhängigkeit abgefragt; die
eigentliche Frage war nie sichtbar gestellt. Die Regel steht jetzt in
CLAUDE.md (Querschnitts-Konventionen): Eine neue Laufzeit oder Sprache wird
als eigene, benannte Entscheidung mit den Alternativen innerhalb der
vorhandenen Technologien vorgelegt — nie als Nebensatz.
Nachgeholt in zwei Schritten — erst die **Beweggründe** (Nutzer: „die
Frage aufsplitten und mich mehr nach Beweggründen fragen, daraus die
eigentliche Entscheidung ableiten"), dann die Entscheidung:
- **Beweggründe:** Entwickler arbeiten parallel an geteilten Plänen und
setzen agentische KI ein — Pläne im Dialog erstellen, Feature-Fortschritt
tracken; der Agent läuft meist lokal (OpenCode, Claude Code, Codex),
später auch im Frontend. Pläne liegen **beides**: als Datei im Repo und
als Server-Dokument. Der Text eines lokalen Plans **darf** zur Analyse
an den eigenen Server. Die Startzeit eines lokalen Prozesses ist egal.
- **Daraus folgt: kein lokaler Prozess, keine neue Laufzeit.** Der
MCP-Server lebt **nur im Backend** (Streamable HTTP, Bearer-Token per
Spring Security), die Werkbaum-Logik läuft dort per GraalJS aus den
unveränderten Frontend-Modulen, beim Bauen zu einem ES-Modul gebündelt —
Node bleibt Build-Werkzeug. Jedes Werkzeug hat zwei Formen: `text` (der
Agent hat die Datei selbst gelesen, bekommt den neuen Text zurück und
schreibt ihn selbst; die Leitplanken sind dort **Befund**) und `document`
(UUID eines Dokuments dieses Backends; Zeilen-Diff, Konfliktschutz,
Leitplanken als **Sperre**). `document` ist bewusst eine UUID, keine URL
— kein Fremdserver, keine SSRF-Frage. Der Git-Spiegel ist `pull-doc`,
vom Cron oder vom Agenten selbst aufgerufen; ein Spiegel am Dienst
entfällt.
- **Verworfen:** das Node-Paket (`#ai.mcp.node` `[-]`); als Rückfall
benannt, nicht gewählt: das Backend-Jar im stdio-Modus beim Entwickler
(keine neue Laufzeit, aber ohne Anlass, sobald der Text zum Server darf).
- **Backend-Abhängigkeiten, Rückfrage gestellt und beantwortet:** GraalJS
(`org.graalvm.polyglot`) und der **Spring-AI-Starter**
(`spring-ai-starter-mcp-server-webmvc`); Boot-4-Verträglichkeit ist beim
Spike zu prüfen, Rückfall das MCP-Java-SDK direkt.
- **Die Runde-1-Entscheidungen zum Node-Paket sind überholt** (SDK,
Serialisierung im Paket, npm); die Tool-Schicht lebt als
`frontend/src/inspect.js`, `guard.js`, `edit.js` — dort, wo `#ai.dialog`
sie ohnehin braucht.
**Vor allem anderen steht jetzt der Spike** (`#ai.mcp.spike`): Läuft das
Bündel in GraalJS mit denselben Zahlen wie Vitest, was kostet ein Kontext
auf dem knappen Host (D76-Nachtrag 3), reicht der Apache den SSE-Strom
ungepuffert durch. Fällt er durch, ist die Antwort nicht Node, sondern eine
neue Frage an den Entwickler. Offen sonst nur die Prompts. Gebaut ist
nichts.
+58 -43
View File
@@ -225,16 +225,18 @@
- [?] #ai.dialog: Edit the plan in a dialogue with the AI (L) - [?] #ai.dialog: Edit the plan in a dialogue with the AI (L)
- [?] #ai.key: Bring your own API key, no server in between (M) - [?] #ai.key: Bring your own API key, no server in between (M)
- [?] #ai.ground: llms.md grounds the model in the notation (S) - [?] #ai.ground: llms.md grounds the model in the notation (S)
- [ ] #ai.mcp: An MCP server hands the plan to outside agents (XL) :#not.llms %% docs/rfc/001-mcp-server.md - [ ] #ai.mcp: An MCP server in the backend hands the plan to outside agents (XL) :#not.llms %% docs/rfc/001-mcp-server.md
- [ ] #ai.mcp.node: A Node package that reuses the one JS parser (S) %% no second parser, D14 - [ ] #ai.mcp.spike: GraalJS runs the one JS parser inside the JVM — measured before anything is built (S) %% memory, same numbers as vitest, SSE through Apache
- [ ] #ai.mcp.read: Resources: the agent guide, a file, a server document (S) - [ ] #ai.mcp.core: The tool layer as headless frontend modules, bundled into the jar (M) :#ai.mcp.spike
- [ ] #ai.mcp.inspect: Tools that answer what the diagram answers (M) %% tree, warnings, cheapest path - [ ] #ai.mcp.read: Resources: the agent guide and the server documents (S)
- [ ] #ai.mcp.write: Writes go out as line diffs and are refused on conflict (M) :#col.live.diff - [ ] #ai.mcp.inspect: Tools that answer what the diagram answers, for a text or a document (M) %% tree, warnings, cheapest path
- [ ] #ai.mcp.guard: Guard rails: no broken notation, no [^] from an agent (S) - [ ] #ai.mcp.write: Writes to documents go out as line diffs and are refused on conflict (M) :#col.live.diff
- [ ] #ai.mcp.verbs: Node verbs: add, move, size, remove — the rules live in the frontend (M) :#ai.mcp.write - [ ] #ai.mcp.guard: Guard rails: no broken notation, no [^] — a block on documents, a finding on text (S)
- [ ] #ai.mcp.mirror: The shared document mirrors into git after every agent write (S) :#col.git.pull - [ ] #ai.mcp.verbs: Node verbs: add, move, size, remove — text to text, and on documents (M) :#ai.mcp.write
+ [?] #ai.mcp.http: Streamable HTTP, served by the backend running the JS modules (M) :#col.live.owner %% GraalJS spike first - [ ] #ai.mcp.auth: A static bearer token via Spring Security; OAuth once there is an IdP (S)
- [-] #ai.mcp.kotlin: A server inside the backend with its own Kotlin parser (L) %% the second grammar D14 forbids - [ ] #ai.mcp.mirror: pull-doc names who changed which version — run by cron or by the agent (S) :#col.git.pull
- [-] #ai.mcp.node: A local Node package as stdio server (L) %% a new runtime, and the text may go to the own server instead — D93
- [-] #ai.mcp.kotlin: A parser of its own in Kotlin (L) %% the second grammar D14 forbids
--- ---
#wb #wb
@@ -1298,39 +1300,50 @@
#ai.mcp #ai.mcp
The other direction of the AI integration: not an LLM inside the editor, The other direction of the AI integration: not an LLM inside the editor,
but the plan handed to agents that live outside it — Claude Code, IDE but the plan handed to agents that live outside it — Claude Code,
agents, desktop assistants — over the Model Context Protocol. Specified in OpenCode, Codex, IDE agents — over the Model Context Protocol, served by
the backend over streamable HTTP. No local process, no new runtime: a
URL and a token in the host's configuration. Specified in
docs/rfc/001-mcp-server.md before anything is built. docs/rfc/001-mcp-server.md before anything is built.
#ai.mcp.node #ai.mcp.spike
The server is a small Node package that imports the frontend's headless The whole design rests on the JVM running the frontend's JS modules
modules (parser, model, live diff) as they are. That keeps D14 intact: the through GraalJS. Before a line is built, a spike measures it: the bundle
one JS parser stays the only parser, and agents get the same warnings, parses the shipped plan with the same numbers vitest reports, a context
effective statuses and cheapest path the diagram shows. costs a known amount of memory on the tight production host, and Apache
passes the SSE stream of a long tool call unbuffered.
#ai.mcp.core
The tool layer — tree to JSON, guard rails, node edits — lives as headless
modules in frontend/src, tested with vitest, and is bundled at build time
into one ES module inside the jar. That keeps D14 intact: the one JS
parser stays the only parser, and Node stays what it is here, a build tool.
#ai.mcp.read #ai.mcp.read
What an agent can read: llms.md as the notation guide, a local .werkbaum What an agent can read: llms.md as the notation guide, and the documents
file, and a shared document on a Werkbaum backend by its URL — the same of this backend by UUID — with title, version and checksum. A local file
three sources a person has. the agent reads itself and hands the text to the tools.
#ai.mcp.inspect #ai.mcp.inspect
Tools that return the parsed tree with ids, statuses (own and effective), Tools that return the parsed tree with ids, statuses (own and effective),
sizes (given and assumed), tags and dependencies, the warnings the editor sizes (given and assumed), tags and dependencies, the warnings the editor
would show, and the cheapest path with its stations and per-person load — would show, and the cheapest path with its stations and per-person load —
so the agent reasons on the model instead of re-implementing the SPEC. for a text the agent passes in or a document it names — so the agent
reasons on the model instead of re-implementing the SPEC.
#ai.mcp.write #ai.mcp.write
Edits leave the server as the same line diffs the editor sends: against a Edits to a document leave the server as the same line diffs the editor
base version with checksum, refused on a real conflict, visible to every sends: against a base version with checksum, refused on a real conflict,
open editor with "changed by" naming the agent. A local file is written visible to every open editor with "changed by" naming the agent's host.
only if it is unchanged since it was read. For a text, the tool returns the new text and the agent writes the file.
#ai.mcp.guard #ai.mcp.guard
Guard rails on every write: the result must not break the notation (no new Guard rails on every write: the result must not break the notation (no new
mixedGate, unknownStatus, descStray or duplicateId), and an agent never mixedGate, unknownStatus, descStray or duplicateId), and an agent never
writes [^] — "in production" is a deploy's statement (D30), not a model's. writes [^] — "in production" is a deploy's statement (D30), not a model's.
Content warnings like a size conflict are written and reported, so a human On a document that blocks the write; on a text it is a finding the agent
sees them as the amber mark. Both rails can be lifted per call, never silently. sees. Content warnings like a size conflict are written and reported, so a
human sees them as the amber mark. Both rails can be lifted per call.
#ai.mcp.verbs #ai.mcp.verbs
Convenience for the model: add a node under a parent, move a subtree, set a Convenience for the model: add a node under a parent, move a subtree, set a
@@ -1338,24 +1351,26 @@
gets wrong. The text-to-text rules live in frontend/src/edit.js beside the gets wrong. The text-to-text rules live in frontend/src/edit.js beside the
fold and short-id helpers, so exactly one place keeps knowing the line format. fold and short-id helpers, so exactly one place keeps knowing the line format.
#ai.mcp.auth
One static bearer token per installation, checked by Spring Security like
the master password; without it the endpoint is closed, not open. OAuth
2.1, which MCP foresees for HTTP, waits for an identity provider.
#ai.mcp.mirror #ai.mcp.mirror
Both at once: the agent works on the shared document, where the people Both at once: the agent works on the shared document, where the people
are, and the same plan stays under git control. After every agent write are, and the same plan stays under git control. pull-doc --git-commit
the server runs pull-doc --git-commit for a configured mirror file, naming archives the server state — run by a cron, or by the agent itself right
the agent in the commit — the server is the source, git the archive. The after its change — and with --with-history the commit names who changed
reverse (git as the source) is a different node, #col.git. which version, as trailers, so blame never credits the agent with a
human's lines. The server is the source, git the archive.
#ai.mcp.http #ai.mcp.node
Streamable HTTP as a second transport, so a hosted agent can reach the A local Node package as stdio server was the first draft: files and the
server without a local process. Served by the Spring backend, which runs git mirror right at the process. Discarded because it would make Node a
the same JS modules through GraalJS — Kotlin is transport and access runtime — a new technology, one dimension heavier than a dependency — and
(a static bearer token via Spring Security), the logic stays in one place. the motives did not need it: the text may go to the own server instead.
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 #ai.mcp.kotlin
Building the server into the Spring backend with a parser of its own would A parser of its own in Kotlin would put the logic next to the documents —
put it next to the documents — but a Kotlin parser is exactly the second but it is exactly the second grammar D14 forbids. Discarded; the backend
grammar D14 forbids. Discarded; the backend may host the transport runs the JS parser instead of rebuilding it.
(#ai.mcp.http), never a second grammar.
+448 -521
View File
File diff suppressed because it is too large Load Diff