`scripts/simulate-teacher.mjs` creates, edits and deletes what teachers
create — course, room, topic, task, board, columns, cards, rich text,
link, Etherpad pad, folder, files — so the MCP server can be exercised
against content the real account has never held. It is the only thing
here that writes to a Schulcloud and refuses any non-localhost address.
`scripts/mcp-env.sh` points the server and CLI at the instance.
Two gaps it exposed in the stack itself:
The files-storage AMQP consumer is a separate entrypoint, and we were
running only the HTTP one. Nothing was bound to the `files-storage`
exchange, so `TaskService.delete` — which awaits deleteFilesOfParent
over AMQP before touching the task — hung until the request timeout.
Deleting any task or topic answered 408 with the entity still there.
The demo data is dated 2017-2018 and the v3 endpoints filter on those
dates, so a student saw no tasks at all. seed.sh now brings courses and
homework into the present, which is the difference between a fixture
that exercises the student-facing surface and one that looks empty.
Teams turn out to be uncreatable through the API (the legacy service
registers no `create`, v3 has no route), and a new board is unpublished
and 403s for students, so the simulation adopts a seeded team and leaves
one board a draft on purpose.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read-only MCP server exposing a Schulcloud account to Claude: courses,
column boards, lessons, tasks, and file downloads with text extraction.
The API surface was verified against the live instance rather than
inferred from upstream source, which changed several design decisions:
- The `jwt` cookie works verbatim as `Authorization: Bearer` and lasts 30
days, so there is no cookie jar and no refresh-session timer.
- Course contents live at /api/v3/course-rooms/{courseId}/board; there is
no GET /api/v3/courses/{id}.
- Files are a separate service (/api/v3/file/*) with its own OpenAPI doc.
- Board file elements carry no file id; attachments are resolved by
listing files-storage with parentType=boardnodes and the element id.
Read-only by construction: every client method is a GET, including the
api_get escape hatch. The endpoint is internet-facing by necessity, so a
leaked token being unable to act as the user is the key safety property.
Deploys as a container behind the Pi's existing Caddy, guarded by a
constant-time bearer check. Stateless — no database.
Verified: 28 unit tests, plus a 30-check end-to-end run driving a real
MCP client over Streamable HTTP against the live account.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>