From 0ccb21303d19d6685721d79d585d93c89bccc1a4 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Sun, 13 Sep 2026 15:19:30 +0200 Subject: [PATCH] docs: MCP beschreiben, Fahrplan abschliessen Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 23 +++++++++ README.md | 7 ++- ROADMAP.md | 18 +++++-- docs/MCP.md | 83 ++++++++++++++++++++++++++++++ docs/handbuch/README.md | 11 +--- src/server/aktionen/zeitbloecke.ts | 19 +++++-- 6 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 docs/MCP.md diff --git a/CHANGELOG.md b/CHANGELOG.md index cff08f7..7205030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,29 @@ die Versionierung [Semantic Versioning](https://semver.org/lang/de/). ## [Unveröffentlicht] +### Hinzugefügt + +- **MCP-Server** unter `/api/mcp` mit sechs Werkzeugen: Aufgaben suchen, + Woche ansehen, Kalender auflisten, Aufgabe anlegen, abhaken und einplanen. + Beschreibung in [docs/MCP.md](docs/MCP.md). +- Die Werkzeugliste richtet sich nach den Rechten des Tokens — was ein Token + nicht darf, sieht das Modell gar nicht erst. + +### Geändert + +- Das Einplanen liegt jetzt an einer Stelle, die sowohl die Oberfläche als + auch der MCP-Server benutzen. Ein über MCP angelegter Zeitblock verhält + sich damit genau wie ein von Hand gezogener. + +### Sicherheit + +- MCP benutzt dieselben Tokens und dieselben Rechte wie die Schnittstelle. + Ein Sprachmodell bekommt keinen eigenen Zugang. +- Jede schreibende Nutzung steht im Protokoll, mit dem Vermerk, dass sie + über MCP kam. +- Der Server kann weder löschen noch Bereiche, Projekte, Kalenderkonten, + Einstellungen oder Tokens anfassen. + ## [0.7.0] – 2026-09-13 ### Hinzugefügt diff --git a/README.md b/README.md index d9d5393..cce4eac 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ wird. Planer lässt sich als App auf dem Startbildschirm ablegen - **Öffentliche Schnittstelle** mit Tokens je Anwendung, Rechten je Token und jeder schreibenden Nutzung im Protokoll ([Beschreibung](docs/API.md)) +- **MCP-Server** für Sprachmodelle — dieselben Tokens, dieselben Rechte + ([Beschreibung](docs/MCP.md)) - Anmeldung über einen OIDC-Anbieter, über Passkeys oder mit E-Mail und Passwort — je Instanz einzeln zuschaltbar - Rollen `Benutzer` und `Verwaltung`; jede schreibende Aktion wird @@ -51,9 +53,10 @@ wird. - Hell und Dunkel, Oberfläche durchgehend auf Deutsch - Betrieb über Docker Compose, mit Sicherung, Aktualisierung und Healthcheck -### Geplant +### Was noch kommen könnte -Ein MCP-Server. Siehe [ROADMAP.md](ROADMAP.md). +Der [Fahrplan](ROADMAP.md) ist abgearbeitet. Was danach denkbar wäre, steht +dort unter „Ideen für später" — und was bewusst nicht kommt, gleich darunter. ## Bildergalerie diff --git a/ROADMAP.md b/ROADMAP.md index 026814a..35c1446 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -12,7 +12,7 @@ grünen Prüfungen, aktueller Dokumentation und einem Eintrag im DEVLOG. | [M5](#m5--benachrichtigungen) | Benachrichtigungen | erledigt | | [M6](#m6--integrationen) | Integrationen | erledigt | | [M7](#m7--schnellerfassung-und-api) | Schnellerfassung und API | erledigt | -| [M8](#m8--mcp) | MCP | geplant | +| [M8](#m8--mcp) | MCP | erledigt | --- @@ -156,9 +156,19 @@ Zeremonie als Schutz. ## M8 – MCP -- [ ] MCP-Server über `@modelcontextprotocol/sdk` -- [ ] Lesen und Anlegen von Aufgaben und Zeitblöcken -- [ ] Rechte wie bei der API, jede Nutzung im Protokoll +**Erledigt am 13.09.2026 mit Version 0.8.0.** + +**Ziel:** Ein Sprachmodell soll den Alltag erleichtern können — ohne einen +eigenen, weicheren Zugang zu bekommen als jede andere fremde Anwendung. + +- [x] MCP-Server über `@modelcontextprotocol/sdk` +- [x] Lesen und Anlegen von Aufgaben und Zeitblöcken +- [x] Rechte wie bei der API, jede schreibende Nutzung im Protokoll +- [x] Werkzeugliste richtet sich nach den Rechten des Tokens + +**Bewusst offen gelassen:** Der Server kann nichts löschen, keine Bereiche +oder Projekte anlegen und keine Einstellungen anfassen. Ein Modell soll den +Alltag erleichtern, nicht die Ordnung umbauen. --- diff --git a/docs/MCP.md b/docs/MCP.md new file mode 100644 index 0000000..1ab1ac9 --- /dev/null +++ b/docs/MCP.md @@ -0,0 +1,83 @@ +# MCP-Server + +Der Planer spricht das Model Context Protocol. Damit kann ein Sprachmodell +Aufgaben suchen, anlegen, abhaken und einplanen — mit denselben Tokens und +denselben Rechten wie die [Schnittstelle](API.md). + +Adresse: `/api/mcp` + +## Einrichten + +1. In der Anwendung unter **API** einen Token anlegen. Gib ihm nur, was das + Modell wirklich braucht. +2. Den Client auf die Adresse zeigen lassen, mit dem Token als Bearer. + +Für Claude Code: + +```bash +claude mcp add planer --transport http https://planer.example.de/api/mcp \ + --header "Authorization: Bearer planer_…" +``` + +Für Clients, die nur stdio können, dazwischen `mcp-remote` setzen: + +```json +{ + "mcpServers": { + "planer": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://planer.example.de/api/mcp", + "--header", + "Authorization: Bearer planer_…" + ] + } + } +} +``` + +## Werkzeuge + +Welche ein Modell zu sehen bekommt, hängt am Token. Was nicht erlaubt ist, +taucht gar nicht erst in der Liste auf — ein Modell soll nicht gegen eine Tür +laufen, die für es verschlossen ist. + +| Werkzeug | Recht | Was es tut | +| -------------------- | ----------- | ----------------------------------------------- | +| `aufgaben_suchen` | `lesen` | Aufgaben auflisten, optional gefiltert | +| `woche_ansehen` | `lesen` | Zeitblöcke einer Woche mit Summe | +| `kalender_auflisten` | `lesen` | Verbundene Kalender samt Kennung | +| `aufgabe_anlegen` | `erfassen` | Neue Aufgabe in der Inbox | +| `aufgabe_abhaken` | `schreiben` | Aufgabe auf erledigt setzen | +| `zeitblock_anlegen` | `schreiben` | Aufgabe einplanen und in den Kalender schreiben | + +Zum Einplanen braucht es die Kennung eines Kalenderkontos — daher erst +`kalender_auflisten`, dann `zeitblock_anlegen`. + +`zeitblock_anlegen` geht durch dieselbe Stelle wie das Ziehen in der +Wochenansicht: Rasterprüfung, Kalenderschreibweg, Schleifenschutz, Protokoll. +Ein über MCP angelegter Zeitblock verhält sich also genau wie ein von Hand +gezogener. + +## Was der Server bewusst **nicht** kann + +- **Löschen.** Weder Aufgaben noch Zeitblöcke. Abhaken ja, wegräumen nein. +- **Bereich oder Projekt setzen.** Was hier entsteht, landet in der Inbox. +- **Bereiche, Projekte oder Kalenderkonten anlegen und ändern.** +- **Einstellungen oder Tokens anfassen.** + +Ein Modell soll den Alltag erleichtern, nicht die Ordnung umbauen. + +## Protokoll + +Jede schreibende Nutzung landet im Protokoll — mit dem Vermerk, dass sie über +MCP kam, und mit dem Token. Ein Modell, das Aufgaben anlegt, muss genauso +nachvollziehbar sein wie ein Mensch. Eher noch mehr. + +## Ohne Sitzungen + +Der Server ist zustandslos: Jede Anfrage steht für sich. Der Planer hat keine +langlaufenden Vorgänge, und eine Sitzungsverwaltung wäre Zustand, den mehrere +Container nicht teilen könnten. diff --git a/docs/handbuch/README.md b/docs/handbuch/README.md index efd439f..be1f0ea 100644 --- a/docs/handbuch/README.md +++ b/docs/handbuch/README.md @@ -31,18 +31,9 @@ Anleitung für den Alltag mit dem Planer. Eine Seite je Thema. 11. **[Protokoll](protokoll.md)** – nachsehen, wer wann was geändert hat -## Was noch kommt - -Diese Seiten entstehen mit den nächsten Meilensteinen: - -| Thema | Meilenstein | -| ----- | ----------- | -| MCP | M8 | - -Der [Fahrplan](../../ROADMAP.md) sagt, was in welchem Meilenstein steckt. - --- Für fremde Anwendungen und Skripte: [Schnittstelle](../API.md). +Für Sprachmodelle: [MCP-Server](../MCP.md). Zur Einrichtung einer neuen Instanz: [Schnellstart](../SCHNELLSTART.md). Für Betrieb, Sicherung und alle Umgebungsvariablen: [README](../../README.md). diff --git a/src/server/aktionen/zeitbloecke.ts b/src/server/aktionen/zeitbloecke.ts index a56001a..f98e020 100644 --- a/src/server/aktionen/zeitbloecke.ts +++ b/src/server/aktionen/zeitbloecke.ts @@ -70,10 +70,10 @@ export async function zeitblockAnlegen(daten: FormData): Promise { const ergebnis = await zeitblockAnlegenFuer( { id: akteur.id, email: akteur.email }, { - aufgabeId: String(daten.get('aufgabeId') ?? ''), - kalenderkontoId: String(daten.get('kalenderkontoId') ?? ''), - beginn: new Date(String(daten.get('beginn') ?? '')), - ende: new Date(String(daten.get('ende') ?? '')), + aufgabeId: feld(daten, 'aufgabeId'), + kalenderkontoId: feld(daten, 'kalenderkontoId'), + beginn: new Date(feld(daten, 'beginn')), + ende: new Date(feld(daten, 'ende')), }, ) @@ -91,6 +91,17 @@ export async function zeitblockAnlegen(daten: FormData): Promise { } } +/** + * Ein Textfeld aus dem Formular. + * + * FormData kann auch Dateien liefern; die haetten hier nichts zu suchen und + * wuerden beim Stringify zu "[object Object]". + */ +function feld(daten: FormData, name: string): string { + const wert = daten.get(name) + return typeof wert === 'string' ? wert : '' +} + // --- Verschieben ----------------------------------------------------------- const verschieben = z.object({