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>
130 lines
4.5 KiB
Markdown
130 lines
4.5 KiB
Markdown
# Running it locally
|
|
|
|
Everything runs on your machine: Postgres, the server, the CLI, and Claude Code
|
|
talking to all of it. Only Schulcloud itself is remote.
|
|
|
|
## Start the stack
|
|
|
|
```bash
|
|
cp .env.example .env # fill in TSC_URL, TSC_JWT_COOKIE, MCP_AUTH_TOKEN
|
|
npm install && npm run build
|
|
docker compose up -d --build
|
|
curl -s http://127.0.0.1:8080/healthz # {"status":"ok","sessions":0,"index":"on"}
|
|
```
|
|
|
|
`docker-compose.override.yml` is merged automatically and is **local-only**: it
|
|
publishes the server on `127.0.0.1:8080` and Postgres on `127.0.0.1:55432`, and
|
|
switches crawling to on-demand. Two `.env` settings adjust it: `MCP_HOST_PORT`
|
|
moves the published port when 8080 is taken, and `CRAWL_INTERVAL_MS` restores a
|
|
crawl timer — worth doing against a real account, because `what_changed` can
|
|
only report what happened between crawls.
|
|
|
|
After pasting a new `TSC_JWT_COOKIE` into `.env`, **recreate** the container —
|
|
`env_file` is read when the container is created, not on restart:
|
|
|
|
```bash
|
|
docker compose up -d --force-recreate schulcloud-mcp
|
|
``` On the Pi neither port is published — Caddy
|
|
reaches the container over the Docker network.
|
|
|
|
Loopback binding is deliberate. The bearer token is the only thing in front of
|
|
your account's data, so it should not be listening on your LAN while you test.
|
|
|
|
## Populate the index
|
|
|
|
A full crawl is ~270 requests; start with one course:
|
|
|
|
```bash
|
|
node dist/bin/cli.js login --server http://127.0.0.1:8080 --token "$MCP_AUTH_TOKEN"
|
|
node dist/bin/cli.js refresh --course <courseId> # or omit --course for everything
|
|
node dist/bin/cli.js status
|
|
```
|
|
|
|
Get a course id from `curl -s -H "Authorization: Bearer $TSC_JWT_COOKIE" \
|
|
"$TSC_URL/api/v3/courses?limit=5" | jq -r '.data[] | "\(.id) \(.title)"'`.
|
|
|
|
## Connect Claude Code
|
|
|
|
Claude Code supports MCP over stdio, SSE and HTTP. The VS Code extension runs
|
|
the same Claude Code underneath, so it reads the same configuration — register
|
|
once and both work.
|
|
|
|
**Use `--scope user`.** Claude Code's default scope is `local`, which is
|
|
*per-project*: the server is stored under that directory in `~/.claude.json` and
|
|
`claude mcp list` will not show it from anywhere else. Since the point is to ask
|
|
about your coursework from wherever you happen to be, user scope is what you
|
|
want. (`project` scope would write a `.mcp.json` into the repo — wrong here,
|
|
because the HTTP config carries a token.)
|
|
|
|
**HTTP — matches the deployed setup:**
|
|
|
|
```bash
|
|
claude mcp add --scope user --transport http schulcloud http://127.0.0.1:8080/mcp \
|
|
--header "Authorization: Bearer $MCP_AUTH_TOKEN"
|
|
```
|
|
|
|
**stdio — no server or Docker needed:**
|
|
|
|
```bash
|
|
claude mcp add --scope user schulcloud-stdio \
|
|
-e DATABASE_URL=postgresql://schulcloud:schulcloud@127.0.0.1:55432/schulcloud \
|
|
-- node --env-file=/absolute/path/to/.env /absolute/path/to/dist/bin/stdio.js
|
|
```
|
|
|
|
`--env-file` keeps the JWT in `.env` rather than copying it into the MCP config.
|
|
The HTTP form has no equivalent — its header holds the token — so that token
|
|
lives in `~/.claude.json`.
|
|
|
|
Check it:
|
|
|
|
```bash
|
|
claude mcp list # schulcloud: http://127.0.0.1:8080/mcp (HTTP) - ✔ Connected
|
|
claude mcp get schulcloud
|
|
```
|
|
|
|
If `claude mcp list` does not show it, the usual cause is scope: run
|
|
`claude mcp list` from the directory you added it in, and if it appears there,
|
|
re-add it with `--scope user`.
|
|
|
|
Then just ask: *"which courses am I in?"*, *"what's due this week?"*, *"find the
|
|
material about Verschlüsselung"*. Inside a session, `/mcp` lists the servers and
|
|
their tools.
|
|
|
|
## Test the CLI
|
|
|
|
```bash
|
|
node dist/bin/cli.js ls --long
|
|
node dist/bin/cli.js sync --dry-run # shows what it would mirror
|
|
node dist/bin/cli.js sync
|
|
```
|
|
|
|
`npm link` puts it on your PATH as `schulcloud`.
|
|
|
|
## Run the test suites
|
|
|
|
```bash
|
|
npm test # 135 offline tests
|
|
npm run smoke # end-to-end against the live instance, live-only mode
|
|
DATABASE_URL=… npm run smoke # end-to-end with the index (57 checks)
|
|
```
|
|
|
|
Store tests need a database and skip without one:
|
|
|
|
```bash
|
|
docker exec schulcloud-mcp-db psql -U schulcloud -d schulcloud \
|
|
-c "CREATE DATABASE schulcloud_test OWNER schulcloud"
|
|
TEST_DATABASE_URL=postgresql://schulcloud:schulcloud@127.0.0.1:55432/schulcloud_test npm test
|
|
```
|
|
|
|
**These tests `TRUNCATE`.** They refuse to run unless the database name contains
|
|
"test", because aiming them at the dev database once was enough — the fixtures
|
|
ended up in real data.
|
|
|
|
## Tearing down
|
|
|
|
```bash
|
|
docker compose down # keep the index and mirror
|
|
docker compose down -v # discard them too
|
|
claude mcp remove schulcloud
|
|
```
|