Read the timetable from WebUntis
Schulcloud holds the material for a lesson but not the lesson: this school's course `times` are empty and it publishes the schedule in WebUntis. So "what do I have today, and has anything been cancelled" was unanswerable, and the timetable cannot be typed into a prompt either — it changes daily. core/untis.ts talks to the API the Untis Mobile app uses, and three tools sit on it: untis_timetable (a day or a range, Entfall, Vertretung, room changes, the notes on each period, inline homework, the period id), untis_homework (the class register's list, which is not Schulcloud's tasks) and untis_lesson_topics (what earlier lessons of a series actually covered, which is what says where a subject got to). Read-only, but not by the Schulcloud client's rule: this API is JSON-RPC, so every call is a POST, reads included. READ_METHODS is the guarantee instead, enforced at the single choke point and asserted by a test. It matters because the key can do what the app can — the live account holds W_OWN_ABSENCE, so the same key could report the user absent. What the live instance taught us, all recorded in docs/API.md: - `startDateTime` ends in Z and is local time. The 08:00 lesson reports 08:00Z, so new Date() would move every lesson by an hour or two. - A substitution is two periods, the original CANCELLED and the replacement IRREGULAR beside it, not one period with a changed teacher. - Announced tests live in the period's info text. The exam module is unused here, so getExams2017 is always empty and that field carries the tests. - A day with no lessons is not a holiday: the weeks this account spends in the company simply have no periods. - `?v=i3.2` is required, or the call fails with a Java NPE reported as -8998. Errors arrive with HTTP 200 and an error member. -8504 is a rejected key and -8524 a drifting clock; the tools name both, because no retry fixes either. Configuration is all four UNTIS_* values or none — three are identifiers and the fourth is a credential, so a half-filled block is a paste that went wrong. Without them the tools are not registered at all, since a tool that can only fail is worse than a missing one. whoami reports the WebUntis identity and survives a dead Schulcloud session, so "is the server reachable" no longer gets a misleadingly total no. mcp-env.sh switches WebUntis off for a fixture run: that key belongs to the real school. 224 tests. All 10 WebUntis smoke checks pass, with a key and without one; the Schulcloud checks in those runs answer 401 because this machine's session is logged out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { SchulcloudClient } from './core/client.ts';
|
||||
import type { SessionKeepalive } from './core/keepalive.ts';
|
||||
import { FileManager } from './core/legacy-files.ts';
|
||||
import { SessionToken } from './core/session-token.ts';
|
||||
import { UntisClient } from './core/untis.ts';
|
||||
import { Indexer } from './indexer/indexer.ts';
|
||||
import { Store } from './store/store.ts';
|
||||
|
||||
@@ -27,6 +28,12 @@ export interface Services {
|
||||
indexer: Indexer | undefined;
|
||||
/** The Schulcloud token, which `/api/token` can replace without a restart. */
|
||||
session: SessionToken;
|
||||
/**
|
||||
* WebUntis, when configured. Process-wide so its master data — 140 subjects,
|
||||
* 216 teachers, every holiday of the school year — is fetched once rather
|
||||
* than per MCP session.
|
||||
*/
|
||||
untis: UntisClient | undefined;
|
||||
/**
|
||||
* Set by the entry point that runs one, for status reports. Created there
|
||||
* rather than here because each entry point logs to a different stream.
|
||||
@@ -44,6 +51,7 @@ export async function createServices(config: Config): Promise<Services> {
|
||||
const files = new FileManager(client);
|
||||
const store = await Store.open(config.databaseUrl);
|
||||
const indexer = store ? new Indexer(client, store, config) : undefined;
|
||||
const untis = config.untis ? new UntisClient(config.untis, config.requestTimeoutMs) : undefined;
|
||||
|
||||
if (!store) {
|
||||
console.warn(
|
||||
@@ -51,7 +59,7 @@ export async function createServices(config: Config): Promise<Services> {
|
||||
'/files, /manifest and refresh_index are unavailable. Set DATABASE_URL to enable them.',
|
||||
);
|
||||
}
|
||||
return { config, client, files, store, indexer, session };
|
||||
return { config, client, files, store, indexer, session, untis };
|
||||
}
|
||||
|
||||
export async function closeServices(services: Services): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user