Files
Schulcloud-MCP/.env.example
MechaCat02 bed3923902 Browse the file manager ("Dateien") as a filesystem
Many teachers never use topics or boards; their material sits in the
course's file area, and the tools answered "0 files" for courses holding
dozens of worksheets — 21 of 26 courses on the live account. Persönliche,
Kurs-, Team- and Geteilte Dateien live in the legacy file store, not in
files-storage, and its service is not in the public ingress. The only way in
is the legacy client: HTML listings, and GET /files/signedurl for a
pre-signed download.

core/legacy-files.ts turns that into one path tree — /my, /courses/<course>,
/teams/<team>, /shared — resolving names that contain "/", ids anywhere in a
path, and wrong or ambiguous names with a message saying what is there. A
listing that does not parse throws; it never reads as an empty folder.

Some of the legacy client's GET routes write (GET /files/share/ mints a
share token), so getFileManagerPage allows only the listing routes, by
pattern. Signed URLs are fetched with no credentials and must be https.

- MCP: fs_list, fs_tree, fs_find and fs_read; get_course lists course files.
- CLI: schulcloud fs ls, tree, find and get, recursive and resumable.
- API: /api/fs/list, tree, find and file.
- Index: the crawl walks the file manager (INDEX_FILE_MANAGER, on by
  default), so search covers the text inside those files and sync mirrors
  them under <course>/Kurs-Dateien.

The local instance gains a fixture for all four areas. It needed a loopback,
so signed URLs open from the host, and a pre-created bucket, since MinIO
does not implement PutBucketCors.

135 tests. Smoke 55/55 live; 57/57 and 55/55 on the local instance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 20:19:16 +02:00

84 lines
3.8 KiB
Plaintext

# ---------------------------------------------------------------------------
# Schulcloud instance
# ---------------------------------------------------------------------------
# Base URL of the instance, no trailing slash.
TSC_URL=https://schulcloud-thueringen.de
# The value of the `jwt` cookie from a logged-in browser session.
# The 30-day `exp` is only a ceiling; the real limit is a 2-hour sliding session
# TTL that the built-in keepalive holds open. IMPORTANT: close the Schulportal
# window after copying this — an open tab shares the session and its auto-logout
# will revoke this token ~2h after login. See docs/AUTH.md.
TSC_JWT_COOKIE=
# ---------------------------------------------------------------------------
# This MCP server
# ---------------------------------------------------------------------------
# Shared secret callers must present as `Authorization: Bearer <token>`.
# REQUIRED for the public deployment — without it the endpoint is open to
# anyone who finds the hostname. Generate one with:
# openssl rand -hex 32
MCP_AUTH_TOKEN=
# Listen address inside the container. Leave as-is when running behind Caddy.
PORT=8080
BIND_HOST=0.0.0.0
# Local Docker only: the loopback port docker-compose.override.yml publishes the
# container on. Default 8080; change it when that port is already taken.
# MCP_HOST_PORT=8080
# ---------------------------------------------------------------------------
# Index and file mirror (optional — without these the server runs live-only:
# search crawls on every call, and the CLI's /api surface is unavailable)
# ---------------------------------------------------------------------------
# Postgres for crawl generations, full-text search and the file mirror index.
# On the Pi, point this at the existing instance with its own database and user.
DATABASE_URL=postgresql://schulcloud:schulcloud@postgres:5432/schulcloud
# Where mirrored file bytes are stored. Needs to be writable by the container.
# MIRROR_DIR=/data/mirror
# Files larger than this are indexed as metadata but not mirrored; they are
# still downloadable, proxied live. Default 64 MiB.
# MIRROR_MAX_BYTES=67108864
# Also index personal files ("Meine Dateien") and submitted / returned work,
# including teacher grade comments. This is what makes "what did the teacher
# say about X" searchable and lets what_changed report a re-grade. Costs roughly
# three extra requests per task on a full crawl, so it is off by default.
# INDEX_PERSONAL_FILES=false
# Also walk the file manager ("Dateien") when crawling: Kurs-Dateien for every
# course, plus Persönliche, Team- and Geteilte Dateien on a full crawl. Many
# teachers keep their material only there, so this is on by default. One page
# load per folder — about 160 on a 26-course account.
# INDEX_FILE_MANAGER=true
# How often to re-crawl on a timer, in ms. Default 21600000 (6h). 0 = on demand
# only. A re-crawl of unchanged content downloads nothing, because Schulcloud
# file records are immutable.
# CRAWL_INTERVAL_MS=21600000
# ---------------------------------------------------------------------------
# Limits (optional — sensible defaults are built in)
# ---------------------------------------------------------------------------
# Largest file download_file will pull, in bytes. Default 25 MiB.
# Videos in Schulcloud routinely exceed this; they are not extractable anyway.
# MAX_DOWNLOAD_BYTES=26214400
# Characters of extracted text returned before truncation. Default 120000.
# MAX_EXTRACTED_CHARS=120000
# Per-request timeout against the Schulcloud API, in ms. Default 30000.
# REQUEST_TIMEOUT_MS=30000
# How often to call refresh-session to hold the session open, in ms. Default
# 1800000 (30 min). Must stay well under the instance's JWT_TIMEOUT_SECONDS —
# 7200s here, readable from GET /api/v3/config/public. Set to 0 to disable.
# KEEPALIVE_INTERVAL_MS=1800000