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>
44 lines
1.2 KiB
YAML
44 lines
1.2 KiB
YAML
# Standalone Compose file for the Pi.
|
|
#
|
|
# If you already run Caddy and PostgreSQL from another Compose project, either
|
|
# merge the `schulcloud-mcp` service below into that project's file, or keep
|
|
# this file separate and attach it to the existing Caddy network — see the
|
|
# `networks` block at the bottom and deploy/Caddyfile.snippet.
|
|
|
|
services:
|
|
schulcloud-mcp:
|
|
build: .
|
|
image: schulcloud-mcp:latest
|
|
container_name: schulcloud-mcp
|
|
restart: unless-stopped
|
|
env_file: .env
|
|
environment:
|
|
PORT: 8080
|
|
BIND_HOST: 0.0.0.0
|
|
# No ports are published to the host: Caddy reaches the container over the
|
|
# shared Docker network, so the only way in from the internet is through
|
|
# Caddy's TLS and this server's bearer check.
|
|
expose:
|
|
- "8080"
|
|
networks:
|
|
- caddy
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
read_only: true
|
|
tmpfs:
|
|
- /tmp
|
|
cap_drop:
|
|
- ALL
|
|
|
|
networks:
|
|
caddy:
|
|
# Set to true once this joins the network your existing Caddy already uses,
|
|
# and change the name to match (`docker network ls` to find it).
|
|
external: false
|
|
name: caddy
|