MCP server for the Danish Aula school platform — TypeScript port of scaarup/aula.
aula-mcp demonstrates solid tool structure with clear naming conventions and comprehensive input schemas. All 12 tools are explicitly defined with names, descriptions, and parameter schemas visible in packages/mcp-server/src/tools.ts. Tool names follow verb_noun patterns (list, get, set, download). Descriptions are present but vary in quality, some are excellent (aula.discover at ~200 chars with context and usage guidance), others are terse (aula.profiles.list at 42 chars, aula.presence.today at 26 chars). Input schemas use proper JSON Schema with type declarations and descriptions for all parameters. However, output schemas are not documented, responses are described only narratively in tool descriptions. Error handling is partially implemented but lacks LLM-friendly recovery guidance. No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Missing: batch operations, parameter format constraints in descriptions, documented output field references for tool chaining.
List calendar events
Returns a typed manifest of the logged-in guardian: children (with names + ids), institutions, API version, detected widgets, and which subordinate aula.* tools to call. Includes a `usage` block with name-resolution and tool-selection rules. Call ONCE per session and reuse the result — do not re-call mid-session.
Get messages from a thread
Download a message attachment to disk
List message threads
Download a post attachment to disk
aula.presence.today has empty input schema ({}) but no documented output structure. Impossible to determine what fields the LLM should expect or chain to dependent tools.
Three tools have descriptions under 50 characters (aula.profiles.list: 42 chars, aula.presence.today: 26 chars, aula.messages.list: 40 chars). These violate the 10 - 1024 character guideline and lack context for LLM selection. E.g., 'Raw profiles.getProfilesByLogin response' does not explain WHEN to call it vs aula.discover.
No output schemas are documented for any tool. LLMs cannot plan downstream calls or extract chaining IDs (e.g., does aula.messages.list return threadId in a way aula.messages.get can consume?). Responses described only narratively.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | <=2025-11-25 | v2 |
List school posts
Set presence template for a child
Daily presence overview
Weekly presence templates
Raw profiles.getProfilesByLogin response — every child + institution.
Escape hatch: call any Aula endpoint by name with arbitrary args. Responses are JSON-serialized as-is. Disabled by default; set AULA_MCP_RAW=1 to enable. Use sparingly — this bypasses the tool schema layer.
No tool annotations present (readOnlyHint, destructiveHint, idempotentHint). Agents cannot distinguish which tools are safe to retry, which are side-effecting, or which modify state. aula.presence.set_template is clearly destructive but unmarked.
Parameter descriptions lack format constraints. E.g., 'week' in aula.presence.week is described as 'ISO week string (YYYY-W##)' but constraint is narrative only, not a regex pattern or enum. 'childId' lacks bounds; 'limit' in list tools lacks min/max.
aula.raw_request exists and accepts arbitrary endpoint names + args. While documented as 'escape hatch' and disabled by default, it bypasses the schema layer entirely. High risk for prompt injection and API misuse if enabled.
Error handling visible in downloadAttachmentToDisk (checks res.ok) but recovery guidance not evident. If attachment fetch fails, LLM receives a raw error with no hint of what to do next (retry? try a different URL? check permissions?).
No batch variants offered for tools agents call in loops (e.g., download multiple attachments requires N sequential calls). aula.messages.get_attachment and aula.posts.get_attachment could benefit from batch_download_attachments.