Dokumentation MCP
planr als MCP-Server
planr lässt sich aus jedem Modell bedienen, das MCP spricht - ohne dass jemand HTTP-Aufrufe von Hand baut. Es gibt zwei Wege hinein, mit denselben Werkzeugen:
| Weg | Wo er läuft | Für wen |
|---|---|---|
POST /api/mcp | in planr selbst, über HTTPS | Custom Connectors, Claude Code, alles Entfernte |
npm run mcp | als Prozess auf dem eigenen Rechner, über stdio | Clients, die nur lokale Prozesse einbinden |
Der entfernte Weg ist der normale. Der lokale bleibt für den Fall, dass ein Client keine URL entgegennimmt.
Endpunkt#
POST /api/mcp, Streamable HTTP, ein JSON-RPC-Umschlag je Anfrage. Ausgewiesen
wird sich mit demselben persönlichen Schlüssel wie bei den übrigen Routen:
Authorization: Bearer planr_...Einen anlegen unter Einstellungen → Schnittstellen-Schlüssel (/konto). Der
Klartext erscheint einmal und danach nie wieder.
Ohne gültigen Schlüssel antwortet der Endpunkt mit 401 und
WWW-Authenticate: Bearer realm="planr" - dort sucht ein MCP-Client nach dem
Anmeldeweg.
In Claude Code#
claude mcp add --transport http planr https://planr-ten.vercel.app/api/mcp --header "Authorization: Bearer planr_..."In Claude Desktop#
Hier klemmt es, und zwar nicht an planr. Ein Custom Connector ist eine URL;
Claude erwartet dahinter OAuth 2.1 mit Dynamic Client Registration oder gar
keine Anmeldung. Einen festen Schlüssel als Kopfzeile mitzugeben
(static_headers) ist bei Anthropic in der Beta und muss freigeschaltet
werden - genau das, was dieser Endpunkt schon annimmt.
Bis das freigeschaltet ist, führen drei Wege zum Ziel:
- den Beta-Zugang für Kopfzeilen anfragen (
mcp-review@anthropic.com), dann trägt jede Lehrkraft ihren eigenen planr-Schlüssel ein und mehr ist nicht zu tun, - planr selbst zum OAuth-Server ausbauen - Neons Managed Better Auth lässt
keine eigenen Plugins zu, das wäre also Handarbeit: Registrierung,
Zustimmungsseite, Token-Ausgabe, PKCE, zwei
.well-known-Dokumente, - übergangsweise den lokalen Weg über stdio nehmen.
Lokaler Weg#
Zwei Umgebungsvariablen:
| Variable | Bedeutung |
|---|---|
PLANR_URL | Adresse der planr-Instanz, z.B. https://planr-ten.vercel.app oder http://localhost:3000 |
PLANR_API_TOKEN | persönlicher Schlüssel aus /konto |
Start von Hand:
PLANR_URL=http://localhost:3000 PLANR_API_TOKEN=planr_... npm run mcpIn einem Client eintragen:
{
"mcpServers": {
"planr": {
"command": "npx",
"args": ["tsx", "/Users/dein-name/Projekte/planr/mcp/server.ts"],
"env": {
"PLANR_URL": "https://planr-ten.vercel.app",
"PLANR_API_TOKEN": "planr_..."
}
}
}
}Fehlt eine der beiden Variablen, sagt der Server beim Start welche und beendet sich - er startet nicht halb.
Werkzeuge#
| Werkzeug | Was es tut | Route dahinter |
|---|---|---|
klassen_auflisten | alle Lerngruppen mit Fach und Jahrgang | GET /api/tu-vorschlag |
naechste_stunde | nächste Doppelstunde mit Stoff, samt Übungsthemen und Auffrischung | GET /api/tu-vorschlag?klasse= |
tag_uebersicht | was an einem Tag ansteht, über alle Lerngruppen | GET /api/stunde?datum= |
anforderungen | Meilensteine mit Anforderungen einer Lerngruppe | GET /api/anforderungen |
stunde_eintragen | Planung einer Doppelstunde eintragen und das Soll festschreiben | POST /api/stunde |
rueckmeldung | nach dem Unterricht melden, was tatsächlich lief | POST /api/rueckmeldung |
stundenplan_entwurf | Stundenplan prüfen und in Klartext zurückgeben, ohne zu schreiben | POST /api/stundenplan |
stundenplan_uebernehmen | Lerngruppe anlegen und die Termine des Schuljahres erzeugen | POST /api/stundenplan |
rahmenplan_lesen | die Themen des Rahmenplans für Fach und Jahrgang, mit Kompetenzen und Richtwert | GET /api/vorlage |
vorlage_entwurf | Stundenliste einer Vorlage prüfen und in Klartext zurückgeben, ohne zu schreiben | POST /api/vorlage |
vorlage_uebernehmen | die Stundenliste in die Vorlage schreiben | POST /api/vorlage |
zettel_lesen | offene Fehler und Wünsche zu planr, mit dem Pfad, auf dem sie notiert wurden | GET /api/zettel |
zettel_antworten | am Eintrag antworten und seinen Stand setzen | POST /api/zettel |
nachzutragen | wo die Rückmeldung fehlt, über alle Lerngruppen | GET /api/alltag?was=nachtraege |
woche_uebersicht | was zwischen zwei Daten ansteht, über alle Kurse | GET /api/alltag?was=woche |
material_zeigen | Material einer Stunde und was daran offen ist | GET /api/alltag?was=material |
material_anlegen | ein Material an eine Stunde hängen | POST /api/alltag |
material_abhaken | kopiert / auf iServ setzen, über den Titel | POST /api/alltag |
tue_erzeugen | TÜ in drillr zur Stunde an diesem Tag | POST /api/alltag |
hausaufgabe_setzen | Aufgabenzahl in selbr stellen oder zurücknehmen | POST /api/alltag |
grobplan_lesen | Einheiten eines Kurses, nummeriert | GET /api/grobplan |
grobplan_aendern | Einheit verlängern/kürzen, Leistung verschieben | POST /api/grobplan |
Die lesenden sind als solche gekennzeichnet - daran erkennt ein Client, was er
ungefragt aufrufen darf. stundenplan_entwurf zählt dazu: er rechnet, schreibt
aber nichts.
grobplan_lesen und grobplan_aendern gehören ebenso zusammen: geändert wird
über die Nummern aus der Leseantwort, nie über Namen. Ein kleines Modell erfindet
Namen, eine Nummer kann es nur richtig oder falsch treffen - und falsch fällt in
der Prüfung auf.
Die beiden Stundenplan-Werkzeuge gehören zusammen. Der Entwurf gibt zurück, was
planr aus der Eingabe verstanden hat, dazu eine Prüfsumme; ohne diese Prüfsumme
schreibt stundenplan_uebernehmen nicht. Dazwischen gehört ein Mensch, der den
Klartext liest - ein Modell liefert plausiblen Unsinn, und aus „9d, Di 1./2."
wird sonst unbemerkt ein Mittwoch.
Lerngruppen heißen wie im Stundenplan (9d). Gibt es dieselbe Lerngruppe in
zwei Fächern, gehört fach dazu; sonst antwortet planr mit „gibt es in mehreren
Fächern". Welche Stunde der Stofffolge an einem Datum liegt, rechnet planr
selbst aus - das Datum genügt, eine Stundennummer braucht niemand mitzuzählen.
Vom Rahmenplan zur Stundenliste#
Der Weg, für den rahmenplan_lesen, vorlage_entwurf und vorlage_uebernehmen
gebaut sind - einmal je Fach und Jahrgang, danach jahrelang wiederverwendet:
rahmenplan_lesenholt die Vorgabe des Landes: Themen mit Kompetenzen und Stundenrichtwert. "Terme und Gleichungen, ca. 40 Stunden" - keine Stundenfolge.- Das Modell übersetzt sie in einzelne Doppelstunden, aus dem Lehrwerk oder dem schulinternen Fachcurriculum. Aus 40 Stunden werden 20 Zeilen mit eigenem Thema: Wiederholung, Minusklammer, Distributivgesetz, Ausklammern.
vorlage_entwurfgibt in Klartext zurück, was planr verstanden hat - je Einheit die Doppelstunden mit ihrer Zählung, dazu der Abgleich mit den Themen des Plans (was fehlt, was über dem Richtwert liegt). Es schreibt nichts.- Ein Mensch liest den Klartext und bestätigt.
vorlage_uebernehmenschreibt, mit der Prüfsumme aus dem Entwurf.
Schritt 4 ist nicht verhandelbar. Bei "ca. 40 Stunden Terme" liegt ein Modell schnell plausibel daneben, und eine falsche Stundenfolge fällt erst im März auf.
Die Vorlage muss vorher in planr angelegt sein (/vorlage/neu) - ein Name, ein
Fach, ein Jahrgang, eine Taktung. Angesprochen wird sie über ihren Namen.
Aufbau#
mcp/api.ts Zugang über HTTP: Adressbau, Anmeldung, Fehler in Klartext
mcp/inprozess.ts derselbe Zugang, aber ohne Netz - für den Endpunkt in planr
mcp/werkzeuge.ts Werkzeugliste mit Eingabeprüfung, je Werkzeug eine Route
mcp/routen.ts Pfad -> Handler, gemeinsam für den MCP-Endpunkt und den Chat im Kurs
mcp/registrieren.ts der Server selbst, unabhängig vom Transport
mcp/server.ts Anschluss über stdio
app/api/mcp/route.ts Anschluss über HTTPFachliche Regeln stehen nicht hier, sondern in lib/stundenApi.ts,
lib/tuApi.ts, lib/stundenplanApi.ts und lib/anforderungenApi.ts. Die Hülle
reicht durch - eine zweite Fassung derselben Regeln wäre eine, die irgendwann
abweicht. Aus demselben Grund ruft der Endpunkt die Routen auf und nicht die
Bibliotheken darunter: in den Routen stehen die Prüfung der Eingaben und die
Form der Antwort.
Der Endpunkt ist zustandslos. Eine Vercel-Funktion lebt für die Dauer einer
Anfrage; eine Sitzung im Arbeitsspeicher fände die nächste Anfrage nicht wieder.
Also je Anfrage ein frischer Server, ohne Sitzungskennung, mit JSON statt
Datenstrom - planr schickt nichts von sich aus. GET und DELETE beantwortet
er deshalb mit 405.
Fehler von planr kommen als Text zurück, nicht als Absturz: das Modell liest
„9a gibt es in mehreren Fächern (Physik, Mathematik) - fach mitgeben" und kann
nachfragen, statt die Verbindung zu verlieren. Eingaben, die schon am Schema
scheitern (ein Datum als 25.08.2026), erreichen planr gar nicht erst.
Weiterlesen