import type { Config } from './config.ts'; 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'; /** * Process-wide singletons. * * The store and indexer are shared across MCP sessions — one index, one crawl * at a time — whereas each session gets its own `ServerContext`. Both are * optional: without `DATABASE_URL` the server runs in live-only mode, and * every feature that needs the index says so rather than failing. */ export interface Services { config: Config; client: SchulcloudClient; /** * The "Dateien" file manager. Process-wide so its short listing cache is * shared: an `ls` in one MCP session and a `schulcloud fs get` from the CLI * then cost one page fetch between them, not two. */ files: FileManager; store: Store | undefined; 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. */ keepalive?: SessionKeepalive; } export async function createServices(config: Config): Promise { const client = new SchulcloudClient(config); // Before anything else reads config.jwt: a token replaced at runtime and // saved may be newer than the one in the environment. const session = new SessionToken(config, client, config.stateDir); await session.load(); const files = new FileManager(client); const store = await Store.open(config.databaseUrl); const untis = config.untis ? new UntisClient(config.untis, config.requestTimeoutMs) : undefined; // The indexer gets the same client, so the class register is read with the // master data the untis_* tools have already paid for. const indexer = store ? new Indexer(client, store, config, { untis }) : undefined; if (!store) { console.warn( '[schulcloud-mcp] no index: search will crawl live on every call, and ' + '/files, /manifest and refresh_index are unavailable. Set DATABASE_URL to enable them.', ); } return { config, client, files, store, indexer, session, untis }; } export async function closeServices(services: Services): Promise { await services.store?.close(); }