Files
Schulcloud-MCP/.env.example
MechaCat02 ab265b5b0c Serve MCP at a secret path, so claude.ai can connect
claude.ai's connector dialog takes a name and a URL. Sending a bearer token
needs a "Request headers" beta most accounts lack, and OAuth is not built
yet, so with MCP_PATH_SECRET set the endpoint is also served at
/<secret>/mcp without the bearer token — a trial until OAuth replaces it.

The path is the credential there. It is compared in constant time, and a
wrong one answers 404 like any unknown path. The config refuses fewer than 32
URL-safe characters and never echoes the value, nothing in the server logs
request paths, and the Caddy snippet rewrites the segment before an access
log entry is written (verified against Caddy 2.11). Claude Code and the CLI
keep the bearer token; DEPLOYMENT.md says what the path trades away.

178 tests. Smoke 76/76 and 74/74 on the local instance.

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

98 lines
4.5 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.
# Needed for the first start. Later tokens go in with `schulcloud token set` or
# the server's /token page, without a restart; a saved newer one wins over this.
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=
# claude.ai only: serve MCP at /<this value>/mcp WITHOUT the bearer token,
# because its connector dialog cannot send a header. The URL becomes the
# credential — see "Connecting Claude" in docs/DEPLOYMENT.md before using it.
# At least 32 URL-safe characters; unset = off. Generate one with:
# openssl rand -hex 32
# MCP_PATH_SECRET=
# Where a Schulcloud token replaced at runtime (`schulcloud token set`, /token)
# is saved, so a restart keeps it. docker-compose.yml sets /data/state; unset =
# replacements last until the next restart.
# STATE_DIR=/data/state
# 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