Skip to main content
Glama
oliverhruby

EduPage MCP Server

EduPage MCP Server

GitHub release Glama Grade Quality gates Security Container security Coverage drift

Project

A Model Context Protocol (MCP) server that exposes the full functionality of the edupage-api Python library to AI agents such as opencode, Claude, Cursor and any other MCP client.

EduPage is a school information system used across Europe. This server lets you query and operate a student / teacher / parent EduPage account directly from your agent: timetables, grades, homework, substitutions, meals (including ordering), messages, rosters, parent child-switching and more — including multiple schools (e.g. two children attending different schools).

⚠️ Unofficial API. Like all EduPage MCP servers, this relies on the community-maintained edupage-api library, which talks to EduPage's undocumented endpoints. Use read-only features freely; use the write features (send_message, meal ordering, child switching) carefully.


Related MCP server: ecole-directe-mcp

Table of Contents


Why another EduPage MCP server?

Two other EduPage MCP servers already exist:

Both are good and I have no affiliation with them — they are simply referenced here for honest comparison. They primarily focus on the read-only surface of the API.

This project deliberately goes further:

Capability

mhlavac

mrtineu (PyPI)

this project

Advanced login — portal auto-detect, 2FA, session id (PHPSESSID)

partial (portal)

basic only

✅

Timetables (own + any teacher/class/room)

✅

✅

✅

Grades (all / by term & year)

✅

✅

✅

Substitutions / timetable changes

✅

✅

✅

Meals — read menu

✅

✅

✅

Meals — choose / sign-off / rate

❌

❌

✅

Send messages (send_message)

✅

❌

✅

Parent student switching (switch to/from student)

partial (list)

❌

✅

Next ringing time / bell schedule

❌

❌

✅

Raw session custom request

❌

❌

✅

Multiple schools (auto-login + discovery)

❌

❌

✅

Role-aware (parent / student / teacher)

❌

❌

✅

Day summaries (one-call daily report)

❌

❌

✅

Homework — body text + attachments

❌

✅

✅

Download a homework file to disk

❌

✅

✅

Key differentiators:

  • Multi-school automatic discovery. Set EDUPAGE_SUBDOMAINS with one shared login and the server auto-discovers students across all schools — no need to maintain a manual "Student → school1, Student → school2" mapping. A student at two schools (e.g. Student at school1 + school2) is found automatically with separate per-school results.

  • Role-aware tools. The server detects whether you're a parent, student, or teacher at each school and behaves accordingly — get_student_timetable switches to the student account for parents, returns direct timetables for students. No tool duplication.

  • Full write surface. Meal ordering/rating, message sending, student switching — the other servers don't cover these.

Comparison checked 2026-09-28. If a row is wrong, please open an issue.


What it provides

A single stdio MCP server exposing 31 tools (published on PyPI as edupage-mcp-full):

  • Authentication — login (by credentials, portal auto-detect, or a PHPSESSID cookie via method=), login_all (multi-school, one call), two_factor_finish (complete a pending 2FA), get_subdomains (schools available to the account with role/user id per school + env config, live subdomain discovery for parents). The former auth_status, user_id, login_auto, login_from_session, and two_factor_check_confirmed tools are folded into these.

  • Timetables — get_my_timetable, get_timetable (teacher/student/class/ classroom; end_date for a range, formerly get_timetable_range), get_student_timetable (student by name, cross-school), get_next_week_timetable, get_next_ringing_time, get_periods, get_school_year

  • Students — find_student (name → person_id, cross-school), get_student_timetable (cross-school, role-aware), scan_students (auto-discover all students across schools), get_my_students (classmates or school-wide for parents), switch_to_student (by id or name, parent only), switch_to_parent, clear_student_cache (force refresh cached student lists)

  • Schools — get_subdomains (subdomains available to the account — live-discovered for parents, limited by EDUPAGE_SUBDOMAINS when set — plus session state per school)

  • Grades — get_grades

  • Timeline / notifications — get_timeline (category= for homework, assignments, absences, events, news, or full history since a date)

  • Homework — get_timeline(category='homework') to list assignments (ids in additional_data.superid), get_homework_material to read one assignment's body text, dates and attachment list (the notification alone only carries the title and due date), download_attachment to save one attachment to disk

  • Substitutions — get_timetable_changes, get_missing_teachers

  • Meals — get_meals, choose_meal, sign_off_meal, rate_meal

  • Day summaries — get_day_summary (one call: timetable, substitutions, missing teachers, grades, meals, homework, assignments, absences, news, events, notifications for a date — "what happened yesterday at school" in a single round trip; each section is isolated so one failure doesn't kill the report). Includes an OpenCode skill (school-day-summary) for human-readable formatting in OpenCode; other clients use the raw JSON directly.

  • Rosters — get_roster (roster_type= for students, all students, teachers, classes, classrooms, or subjects), get_my_students

  • Actions — send_message, switch_to_student, switch_to_parent, custom_request


Getting started

You need an MCP-capable client (opencode, Claude Desktop, Cursor, etc.).

1. Install

If you are using an AI coding client, a simple prompt is often enough to get started, for example: "Install the EduPage MCP as described in this GitHub repository oliverhruby/edupage-mcp". Most MCP-capable clients can then guide you through the available setup options.

Option A — from MCP Registry (recommended, one-click in VS Code / GitHub Copilot)

The server is listed in the MCP Registry. In VS Code or GitHub Copilot, search for "EduPage MCP" and install with one click. Or use the direct deeplink: mcp://install/io.github.oliverhruby/edupage-mcp

Option B — from PyPI

Use this for normal usage with a released version.

Requirements: uv for uvx, or Python 3.10+ for pip.

uvx edupage-mcp-full
# or, if you prefer pip (into whatever environment your MCP client uses):
pip install edupage-mcp-full

uvx runs the package without a persistent install. If uvx is unavailable, install uv first (pip install uv or winget install astral-sh.uv).

Option C — from GitHub (latest source)

Use this if you want the latest changes before a PyPI release.

Requirements: uv for uvx, or Python 3.10+ for pip.

uvx --from "git+https://github.com/oliverhruby/edupage-mcp.git" edupage-mcp-full
# or
pip install "git+https://github.com/oliverhruby/edupage-mcp.git"

Option D — Docker

Use this for an isolated container runtime.

Requirements: Docker.

Pull a prebuilt image (recommended):

docker pull ghcr.io/oliverhruby/edupage-mcp:latest

docker run --rm -i \
  -e EDUPAGE_USERNAME=your_username \
  -e EDUPAGE_PASSWORD=your_password \
  ghcr.io/oliverhruby/edupage-mcp:latest

Version tags are also available (for example v0.4.0) if you prefer pinned images.

Build locally from source (fallback):

docker build -t edupage-mcp-full .

docker run --rm -i \
  -e EDUPAGE_USERNAME=your_username \
  -e EDUPAGE_PASSWORD=your_password \
  edupage-mcp-full

The container uses the same environment variables described in Configure credentials. It also includes a HEALTHCHECK (stdio process liveness by default; local TCP check in HTTP transport modes).

For HTTP transports, set optional runtime vars:

  • MCP_TRANSPORT: stdio (default), sse, or streamable-http

  • MCP_HOST: bind host (default 127.0.0.1)

  • MCP_PORT: bind port (default 8000)

  • MCP_API_KEY: optional bearer token for HTTP auth

When MCP_API_KEY is set, HTTP requests must include Authorization: Bearer <key>. If MCP_API_KEY is not set, HTTP endpoints are unauthenticated. For production, prefer proper authentication and TLS via a reverse proxy or API gateway.

pyproject.toml pins mcp<2 (the stable FastMCP v1 API). mcp 2.x renamed FastMCP to MCPServer and changed the API surface; this server targets the FastMCP v1 API for simplicity and stability.

Option C — development from source

Use this if you are contributing or debugging locally.

Requirements: Python 3.10+.

git clone https://github.com/oliverhruby/edupage-mcp.git
cd edupage-mcp
uv sync               # or: python -m venv .venv && .venv/bin/python -m pip install -e .
uv run edupage-mcp-full

Option D — Docker

Use this for an isolated container runtime.

Requirements: Docker.

Pull a prebuilt image (recommended):

docker pull ghcr.io/oliverhruby/edupage-mcp:latest

docker run --rm -i \
  -e EDUPAGE_USERNAME=your_username \
  -e EDUPAGE_PASSWORD=your_password \
  ghcr.io/oliverhruby/edupage-mcp:latest

Version tags are also available (for example v0.4.0) if you prefer pinned images.

Build locally from source (fallback):

docker build -t edupage-mcp-full .

docker run --rm -i \
  -e EDUPAGE_USERNAME=your_username \
  -e EDUPAGE_PASSWORD=your_password \
  edupage-mcp-full

The container uses the same environment variables described in Configure credentials. It also includes a HEALTHCHECK (stdio process liveness by default; local TCP check in HTTP transport modes).

For HTTP transports, set optional runtime vars:

  • MCP_TRANSPORT: stdio (default), sse, or streamable-http

  • MCP_HOST: bind host (default 127.0.0.1)

  • MCP_PORT: bind port (default 8000)

  • MCP_API_KEY: optional bearer token for HTTP auth

When MCP_API_KEY is set, HTTP requests must include Authorization: Bearer <key>. If MCP_API_KEY is not set, HTTP endpoints are unauthenticated. For production, prefer proper authentication and TLS via a reverse proxy or API gateway.

pyproject.toml pins mcp<2 (the stable FastMCP v1 API). mcp 2.x renamed FastMCP to MCPServer and changed the API surface; this server targets the FastMCP v1 API for simplicity and stability.

2. Configure credentials

Either set environment variables or pass credentials to login (see Prompt examples).

# Windows (persistent, per-user)
setx EDUPAGE_USERNAME "your_username"
setx EDUPAGE_PASSWORD "your_password"
setx EDUPAGE_SUBDOMAINS "s1,s2,s3"       # optional: multiple schools (auto-login + discovery)

# macOS / Linux
export EDUPAGE_USERNAME="your_username"
export EDUPAGE_PASSWORD="your_password"
export EDUPAGE_SUBDOMAINS="s1,s2,s3"     # optional

Single school? Just set EDUPAGE_USERNAME + EDUPAGE_PASSWORD. The server auto-discovers your school via the EduPage portal on startup — no subdomain needed.

Multiple schools? Add EDUPAGE_SUBDOMAINS (comma-separated). The server logs into all of them on startup with your shared credentials.

3. Register with your MCP client

opencode — add to ~/.config/opencode/opencode.json (or opencode.jsonc):

{
  "mcp": {
    "edupage": {
      "type": "local",
      "enabled": true,
      "command": ["uvx", "edupage-mcp-full"],
      "env": {
        "EDUPAGE_USERNAME": "{env:EDUPAGE_USERNAME}",
        "EDUPAGE_PASSWORD": "{env:EDUPAGE_PASSWORD}",
        "EDUPAGE_SUBDOMAINS": "{env:EDUPAGE_SUBDOMAINS}"
      }
    }
  }
}

Put credentials in your shell/environment (or a .env) and reference them with {env:VAR}, or hardcode them under env: directly. uvx will auto-provision the package the first time; it must be on your PATH.

Claude Desktop / Cursor — use claude_desktop_config.json / .mcp.json with a mcpServers entry in the standard shape, pointing command/args at the venv python and the edupage_mcp.py path, plus an env block with your credentials.

After editing client config, restart the client so the MCP server is loaded.


Prompt examples

User prompt

Likely tool call(s)

Expected response

"Are we connected and logged in?"

get_subdomains

Available school subdomains (live-discovered for parents), active school/subdomain, env config, and login state per school.

"What classes do I have today?"

get_my_timetable

A short timetable summary for today.

"Show me the 9.A schedule for 2026-09-10"

get_timetable target_type="class" target_id="9.A" date_str="2026-09-10"

Class timetable for that date.

"What grades do I have this term?"

get_grades term="FIRST" year=2026

Subject-by-subject grade overview for the selected term/year.

"Any substitutions today?"

get_timetable_changes

Changes, cancellations, and replacements for today.

"What is for lunch and order option 2 for tomorrow"

get_meals → choose_meal date_str="2026-09-10" meal_type="lunch" number=2

Meal menu and order confirmation (or a clear error if unavailable).

"Find Student A's timetable for tomorrow"

get_student_timetable name="Student A" date_str="2026-09-10"

Student A's timetable; if found in multiple schools, one result per school.

"List teachers and send a hello to Teacher456"

get_roster roster_type="teachers" → send_message recipient_id="Teacher456" body="Hello!"

Teacher list plus message sent confirmation.

"What happened at school yesterday for my kids?"

get_day_summary date_str="2026-09-09" (discovery index) → get_day_summary date_str="2026-09-09" name="Student A" subdomain="school-a" → ... name="Student B" subdomain="school-b"

Discovery-first: the no-name call lists each child per school; then one complete daily report call per child (timetable, substitutions, missing teachers, grades, meals, homework, assignments, absences, news, events, notifications). Keeps each response small and avoids mixing schools/students.

"How was school today for Student A?"

get_day_summary name="Student A" (defaults to today)

Human-readable summary via the bundled OpenCode skill school-day-summary.


Multiple schools & automatic student discovery

Each subdomain (school) keeps its own logged-in session. There are two ways to log in to several schools at once:

A) Automatic on startup (recommended). Set EDUPAGE_SUBDOMAINS (a comma-separated list) plus the shared EDUPAGE_USERNAME / EDUPAGE_PASSWORD — the server logs into all of them when it launches, so every tool is immediately ready and students are discoverable across all schools with no login call and no student→school mapping:

setx EDUPAGE_SUBDOMAINS "school1,school2,school3"   # Windows
export EDUPAGE_SUBDOMAINS="school1,school2,school3" # macOS / Linux
get_subdomains   # lists school1, school2, school3 (logged in, with role)
scan_students      # discovers Student A and Student B across those schools
get_student_timetable name="Student A"   # is found at school1 AND school2

B) On demand with login_all. Authenticate several schools at once, then pass subdomain to any data tool (it defaults to the last active subdomain when omitted):

login_all subdomains="school1,school2" usernames="u1,u2" passwords="p1,p2"

get_my_timetable subdomain="school1"
get_my_timetable subdomain="school2"
get_subdomains         # shows all logged-in subdomains + which is active

You can also call login once per school to add/lookup sessions incrementally.

Single school? No EDUPAGE_SUBDOMAINS needed — the server auto-discovers your school via the portal on startup. For two or more schools, set EDUPAGE_SUBDOMAINS (auto-login) or use login_all / repeated login calls.


Students by name (e.g. "timetable for Student A")

Because the server auto-discovers students across the configured EDUPAGE_SUBDOMAINS (or every logged-in school when the variable is unset), you don't need to know or state which school a student is in. Just ask for the timetable by name and the server searches every school in scope:

"timetable for Student A"  ->  get_student_timetable name="Student A"

get_student_timetable (with no subdomain):

  1. searches every school in the discovery scope — the configured EDUPAGE_SUBDOMAINS, or all logged-in schools when unset — for a student whose first/last/full name matches (scan_students does just the discovery step),

  2. for each school where the student is found, switches to the student account if you're logged in as a parent, returns that student's timetable for the date, and switches back to the parent account afterwards,

  3. returns one result per school.

A student attending more than one school (e.g. Student at school1 + school2) therefore yields a list of two per-school timetables — separate results, never merged. This is the built-in replacement for maintaining a manual "Student → school1" mapping: with EDUPAGE_SUBDOMAINS set, discovery is fully automatic.


Tool reference

Tool

Description

Writes?

login

Log in with username/password; method="credentials" (default), method="auto" (portal auto-detect, formerly login_auto), or method="session" with a PHPSESSID cookie (formerly login_from_session). Env vars supported.

✅ session

login_all

Log in to multiple schools in one call

✅ session

two_factor_finish

Finish a pending 2FA login (email/app code or poll_seconds device confirmation; formerly two_factor_check_confirmed + two_factor_finish)

✅ session

get_subdomains

Subdomains available to the account (live-discovered for parents; limited to EDUPAGE_SUBDOMAINS when set) + role/user id per school, active subdomain, failed logins, env config (formerly auth_status, user_id)

get_school_year

Current school year

get_my_timetable

Logged-in user's timetable for a date

get_timetable

Timetable of a teacher/student/class/classroom; end_date for a daily range (formerly get_timetable_range)

get_student_timetable

Student's timetable by name or id (role-aware, cross-school)

✅ session

get_next_week_timetable

Mon–Fri timetable for next week

get_next_ringing_time

Next bell (break/lesson) at a given time

get_periods

Bell schedule (period start/end times)

get_grades

Grades, optionally by year & term

get_timeline

Timeline notifications, one category= at a time: recent, history (since date_from), homework, assignments, absences, events, news (formerly get_notifications, get_notification_history, get_homework, get_assignments, get_absences, get_upcoming_events, get_news)

get_homework_material

Full body text, dates and attachment list of one assignment, by superid from get_timeline(category='homework') → additional_data.superid

download_attachment

Download any authenticated attachment to disk — absolute URL, or the relative /elearning/… key from get_timeline; refuses school/login pages (default <tempdir>/homework, never overwrites)

✅ disk

get_timetable_changes

Substitutions / timetable changes for a date

get_missing_teachers

Teachers missing on a date

get_day_summary

One-call daily report (timetable, substitutions, teachers, grades, meals incl. breakfast/dinner when published, homework, assignments, absences, news, events, notifications) for a date; student by name/id (role-aware). Discovery-first: parent without name/student_id returns a lightweight per-school student index (mode:"discovery"); pass full=True to build full reports for every child. Bundles OpenCode skill school-day-summary for human-readable output.

get_meals

Meal menu (all 5 slots: breakfast, snack, lunch, afternoon snack, dinner)

choose_meal

Order a meal

✅

sign_off_meal

Cancel an ordered meal

✅

rate_meal

Rate a meal (quality/quantity)

✅

get_roster

One roster_type= at a time: students (logged-in user's class), all_students (whole school, short list), teachers, classes, classrooms, subjects (formerly get_students, get_all_students, get_teachers, get_classes, get_classrooms, get_subjects)

get_my_students

Students visible to the logged-in account (one school)

find_student

Look up a student's person_id by name (cross-school)

scan_students

Auto-discover students across the configured EDUPAGE_SUBDOMAINS (or all logged-in schools when unset)

clear_student_cache

Clear cached student rosters (one school or all schools)

✅ cache

get_subdomains

Available school subdomains + role/session per school

send_message

Send a message to a user

✅

switch_to_student

Switch to a student account by id or name (parent only)

✅ session

switch_to_parent

Switch back to the parent account

✅ session

custom_request

Raw text request through the active session (GET/POST); refuses binary bodies and points at download_attachment

✅


Data & safety notes

  • Most tools are read-only. The ones marked Writes? ✅ mutate EduPage state (sent messages, ordered meals, switched accounts). Use them with care.

  • download_attachment is the one tool that writes to local disk: it saves into dest_dir (default <tempdir>/homework), takes the name from the server's content-disposition when present, and appends (1), (2), ... rather than overwriting anything. An HTML login/error page is refused instead of being written out as an attachment.

  • custom_request returns text only: a binary body is refused with a pointer to download_attachment, and a text body over 60000 characters comes back truncated with an explicit truncated: true.

  • get_timeline categories homework, assignments, absences, events and news derive their data from the timeline notifications — if the school doesn't push certain event types, those categories may return empty lists.

  • get_missing_teachers is marked experimental upstream (parses HTML from the substitution page) and can raise if a teacher's name no longer matches.

  • Meal rate_meal and ordering depend on the school publishing menus with the matching identifiers; not all schools expose ratings.

  • get_meals first tries the per-student meal-ordering endpoint (needed for ordering/ratings). When a school doesn't enable that, it falls back to the school's public canteen menu widget (/menu/?wid=menu_CanteenMenu_1). All five slots (breakfast/snack/lunch/afternoon_snack/dinner) are always returned; slots the school doesn't publish are None.


Skills

The package includes an OpenCode skill (school-day-summary) at <site-packages>/edupage_mcp/skills/school-day-summary/SKILL.md. It teaches OpenCode agents how to turn get_day_summary JSON into a human-readable daily school report.

OpenCode only: To register it:

mkdir -p ~/.config/opencode/skills/school-day-summary
cp <site-packages>/edupage_mcp/skills/school-day-summary/SKILL.md \
   ~/.config/opencode/skills/school-day-summary/SKILL.md

Restart OpenCode; the agent can then answer "what happened at school yesterday for my kids?" by calling get_day_summary per child.

Other MCP clients (Copilot, Claude, Cursor, etc.) — call get_day_summary directly; they receive the full structured JSON. Formatting is client-specific (no skill system in the MCP protocol).


Contributing

Contributor and maintainer guidance is in CONTRIBUTING.md.

  • Contribution workflow and local setup

  • Architecture and implementation details

  • Release process (PyPI, GitHub Releases, GHCR)

  • CI quality gates and upstream coverage drift checks


Limitations

  • Unofficial/read-mostly by design. EduPage can change its endpoints at any time; reliability ultimately depends on edupage-api, not this wrapper.

  • No CAPTCHA bypass. If EduPage presents a CAPTCHA during login, log in via browser first, then use login method="session" with the resulting PHPSESSID.

  • 2FA requires human interaction (approve on device or provide a code).

  • Parent/teacher accounts are only partially verified upstream; some parent methods are best-effort.

  • The auth session lives for the lifetime of the MCP server process; restarting the client means logging in again.

  • Cross-school student discovery depends on being logged into all relevant schools (via EDUPAGE_SUBDOMAINS, login_all, or repeated login calls). If a school is not logged in, that student's results from that school cannot be discovered.


Support

If you like this project and want to support or request a feature, send me a beer, it keeps my mind relaxed and ideas will come :-)

Support via PayPal


License

MIT © Oliver Hrubý

This project is not affiliated with or endorsed by Ascora (EduPage) or by the authors of edupage-api. EduPage is a registered trademark of its respective owner(s).

Available Tools

31 tools
choose_mealA
Idempotent

Order/choose a meal. Writes: books the selected menu for the date.

Args: date_str: YYYY-MM-DD to order for. meal_type: 'snack' | 'lunch' | 'afternoon_snack'. number: 1-based menu choice among the chooseable menus (see get_meals). subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'ordered': True, meal_type, date, number}.

Notes: - Read get_meals first for the date to pick a valid number. - To cancel, use sign_off_meal.

ParametersJSON Schema
NameRequiredDescriptionDefault
numberYes
date_strYes
meal_typeYes
subdomainNo

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false, destructiveHint=false, idempotentHint=true and openWorldHint=true, so the safety profile is covered; the description adds that it books a menu for a date and documents the prerequisite read. It does not discuss reversibility or what happens on a repeat call (relevant given idempotentHint=true), so it is strong but not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the one-line purpose and write semantics, then uses tidy Args/Returns/Notes sections. No sentence is filler; each adds invocation-relevant detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the Returns block supplies the response shape, and the Notes cover the key prerequisite and the cancel path. For a 4-param mutation tool with annotations already disclosing safety, nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden, and it does: date_str format (YYYY-MM-DD), the enumerated meal_type values, the meaning of number (1-based choice among chooseable menus with a pointer to get_meals), and the subdomain default. Every parameter is explained beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Order/choose a meal') and immediately clarifies the write semantics ('books the selected menu for the date'). It is clearly distinguishable from the sibling read tool get_meals and the cancel tool sign_off_meal.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section gives explicit ordering guidance ('Read get_meals first for the date to pick a valid number') and names the alternative for the opposite action ('To cancel, use sign_off_meal'). Both when-to-use and alternative routing are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

clear_student_cacheA
Idempotent

Force refresh of cached student data. Writes: drops the local cache so the next student lookup re-fetches from EduPage. Call this after students are added/removed from a school, or if scan_students/find_student seems stale.

Args: subdomain: School whose cache to clear. Without it, clears ALL schools.

Returns: dict: {'cleared': <subdomain|'all'>, 'entries_removed': }.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false and destructiveHint=false, but the description adds the concrete behavior: it drops the local cache so the next lookup re-fetches from EduPage. This explains the side effect precisely – a local, non-destructive mutation – which the annotations alone do not convey. Also documents the return shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the action and effect, then gives scope and the crucial default behavior, then return format. Every sentence adds necessary information without padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for a single-parameter mutation tool with no output schema. It covers the side effect, default scope, triggers, and return keys. Minor deduction because there is no output schema and the description still supplies the return shape, which is helpful but could be considered beyond strict necessity; however, nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry the full parameter burden. It does: 'subdomain: School whose cache to clear. Without it, clears ALL schools.' This is essential because omitting subdomain triggers a global cache clear, a high-impact behavior not evident from the bare 'string/null' schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Uses a specific verb+resource ('Force refresh of cached student data') and clarifies it is a cache write, not a read. Distinguishes clearly from siblings like scan_students and find_student by naming them as stale-data indicators.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to call it – after students are added/removed, or if scan_students/find_student seems stale. No ambiguity about appropriate timing relative to the read tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

custom_requestA
Destructive

Send a raw request to the Edupage server using the active session. Can perform writes depending on the endpoint — treat as write-capable.

Args: url: Absolute URL, or a path like '/export/ajax_prevedene_meno.php' (resolved against https://<subdomain>.edupage.org). method: 'GET' or 'POST'. data: Request body (for POST). headers: JSON string of extra headers, e.g. '{"Accept": "application/json"}'. subdomain: School whose session to use (defaults to the active).

Returns: dict: {'status_code': int, 'content_type': str, 'bytes': int, 'text': str, 'truncated': bool}. bytes is the raw body length and text is it decoded.

Notes: - Low-level escape hatch for endpoints not covered by the dedicated tools — prefer those when available. Parse the returned text yourself; fields are not pre-serialized. - Text only. A binary body (an Office/PDF/image/video attachment) is refused rather than returned as lossily-decoded errors="replace" garbage; use download_attachment(url=…, subdomain=…), which writes the raw bytes to disk and accepts any authenticated attachment URL. - text is capped at 60000 characters so the cap is this tool's, not the MCP client's silent one. EduPage's own pages ignore Range, so a truncated text body cannot be paged through: narrow the request instead (a more specific path or a dedicated tool), or save the whole body with download_attachment.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYes
dataNo
methodYes
headersNo{}
subdomainNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructive/openWorld/write-capable, and the description reinforces rather than merely repeats them by disclosing the text-only constraint, the 60000-char cap being tool-imposed, the inability to page through truncated bodies (EduPage ignores Range), and that the caller must parse text themselves. These are non-obvious behaviors beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then Args/Returns/Notes blocks — well-organized and mostly waste-free. It is long, and the opening 'treat as write-capable' line lightly overlaps the destructiveHint annotation, but the length is largely justified by the 0% schema coverage.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description fully specifies the return dict (status_code, content_type, bytes, text, truncated) and the meaning of `text`/`bytes`, plus the truncation and binary limitations. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the burden — and it does: it documents url path resolution against the subdomain host, method values 'GET'/'POST', data as the POST body, headers as a JSON string with a concrete example, and subdomain defaulting to the active session.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Send a raw request to the Edupage server using the active session') and explicitly distinguishes itself from the dedicated sibling tools ('prefer those when available'), which is exactly the differentiation an agent needs to choose this escape hatch over the ~30 named siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use (endpoints not covered by dedicated tools), when-not (binary bodies go to download_attachment, prefer dedicated tools), and what to do when truncated (narrow the request or use download_attachment). Alternatives and exclusion conditions are named, not inferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

download_attachmentA
DestructiveIdempotent

Download one attachment from the school to disk. Writes: creates a local file.

Saves any authenticated attachment — homework material, message and event attachments alike, which is what get_timeline puts in additional_data.attachements and get_homework_material lists — as the raw response bytes, never overwriting (a ' (1)', ' (2)', ... suffix is appended instead). School pages are not attachments: an HTML or login page is refused rather than written out. Because the bytes go to disk untouched, this is the only tool that returns a binary attachment intact; custom_request refuses one.

Args: url: Absolute attachment URL, or a school-relative path such as '/elearning/ruqjzfpv?z%3A…' — the exact form get_timeline(category='recent') returns in additional_data.attachements — resolved against the school origin. Any authenticated attachment URL works, not just homework: message and event attachments included. The request is authenticated with the school's session. dest_dir: Directory to save into, created when missing. Defaults to <tempdir>/homework (e.g. .../AppData/Local/Temp/homework). filename: Save under this name instead of the server-suggested one. Directory components and characters illegal on the filesystem are stripped, so the file always lands directly inside dest_dir. subdomain: School whose session authenticates the request (defaults to the active subdomain).

Returns: dict: {'saved_to', 'bytes', 'source_url', 'name'}.

Notes: - The only tool in this server that writes to disk, and the only one excluded from the read-only e2e suite — call it only on request. - Verified live 2026-10-02: a bad or expired attachment token is a plain HTTP 404, and a school page fetched without a session answers HTTP 200 with the login page. Both raise instead of writing a file, so a saved attachment is always real bytes. - The name comes from content-disposition when the server sends it, otherwise from the URL path. - Bounded by the library session's 5 s request timeout (Edupage(request_timeout=5)), so very large attachments can fail.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYes
dest_dirNo
filenameNo
subdomainNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: discloses non-overwriting with a ' (1)', ' (2)' suffix, that bytes are written raw and unmodified, that bad/expired tokens return HTTP 404 while an unauthenticated school page returns HTTP 200 with a login page (and both raise rather than write), and the 5 s request-timeout ceiling. That is exactly the destructive/auth/failure context an agent cannot get from the readOnly/destructive/openWorld hints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and the write warning, then cleanly sectioned into Args/Returns/Notes. It is long and repeats itself somewhat (the 'any authenticated attachment, not just homework' point appears twice), but nearly every sentence carries operational information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having no output schema, it lists the returned dict keys ({'saved_to','bytes','source_url','name'}) and covers defaults, filename derivation from content-disposition, error modes, and a timeout limitation. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% (only titles, no descriptions), so the description carries the full burden and does so for all four parameters: `url` (absolute vs. school-relative path with the exact form `get_timeline` returns), `dest_dir` (default `<tempdir>/homework`, auto-created), `filename` (illegal characters stripped, always lands directly in `dest_dir`), and `subdomain` (defaults to the active subdomain).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb + resource: 'Download one attachment from the school to disk. Writes: creates a local file.' It explicitly distinguishes itself from siblings by being the only tool that returns a binary attachment intact, noting that `custom_request` refuses one and pointing at `get_timeline`/`get_homework_material` as the sources of valid URLs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a real gating rule ('the only tool in this server that writes to disk ... call it only on request') and clarifies that school/login pages are not valid input, which steers the agent away from misuse. It stops short of an explicit when-to-use-X-instead-of-Y routing statement, but the contrast with `custom_request` supplies most of that context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_studentA
Read-onlyIdempotent

Look up a student by first/last/full name using tiered matching. Read-only. Without a subdomain, searches ALL logged-in schools and returns one result per school where the student is found.

Args: name: First, last or full student name (also 'Novák V.' short names). subdomain: Restrict the search to one school (default: all logged-in schools in scope).

Returns: dict with results: [{name, student_id, class_id, subdomain, tier, confidence}] sorted by confidence. Tiers: 1=exact, 2=first name, 3=last name, 4=substring.

Notes: - Use a student_id from the results with get_student_timetable / get_day_summary for unambiguous lookups. - Ambiguous matches surface all candidates instead of guessing.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
subdomainNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (which already declare read-only, idempotent, open-world), the description discloses cross-school search scope, the four-tier matching semantics, confidence-based sorting, and the anti-guessing ambiguity policy. This is rich behavioral context an agent cannot infer from structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded and the Args/Returns/Notes structure is easy to scan. Slightly verbose — 'Read-only' restates the annotation, and the sections add length — but nearly every line carries useful information, so waste is minimal.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description still documents the return shape (results list with name, student_id, class_id, subdomain, tier, confidence) and tier definitions, plus downstream usage. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries the full burden, and it does: name accepts first/last/full and short forms like 'Novák V.', and subdomain restricts to one school with default meaning all logged-in schools. Both parameters are fully explained beyond the bare schema types.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (look up), resource (student), and mechanism (tiered matching by first/last/full name). The scope statement about searching ALL logged-in schools without a subdomain further distinguishes it from siblings like get_my_students and scan_students.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section tells the agent to reuse a student_id from results with get_student_timetable/get_day_summary for unambiguous lookups, and explains that ambiguous matches surface all candidates. No explicit exclusion against alternative lookup tools, but context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_day_summaryA
Read-onlyIdempotent

One-call daily school report for a date (default today): timetable, substitutions, missing teachers, grades received that day, meals, homework, assignments, absences, news, events, and timeline notifications.

Composes the individual section tools so you don't need to fire 8-10 calls to answer "what happened yesterday at school" or "what's coming tomorrow".

  • If name/student_id is provided: report for that specific student (found across all schools unless subdomain scopes it).

  • If omitted: discovery-first — for a parent this returns a lightweight per-school index of the account's children (no per-child section fetching), so you can then call per child with name/student_id. Set full=True to instead build the full report for every child.

  • If omitted and logged in as student/teacher: report on the logged-in account. Every section is isolated — a failure in one section yields {"ok": false, "error": ...} without failing the report.

Args: date_str: YYYY-MM-DD to report on (default today). name: Student to report for, by first/last/full name. Ambiguous names surface every candidate instead of guessing. Ignored when student_id is given; see find_student to resolve an id first. student_id: person_id of the student (preferred, unambiguous). Found across all schools unless subdomain scopes the lookup. subdomain: School to report on (defaults to the active subdomain). full: When no name/student_id is given and the account is a parent, build the full report for every child instead of returning the lightweight per-school index. Costs one section sweep per child.

ParametersJSON Schema
NameRequiredDescriptionDefault
fullNo
nameNo
date_strNo
subdomainNo
student_idNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already carry readOnly/idempotent/non-destructive/openWorld, so the description adds meaningfully beyond them: it discloses that every section is isolated and a failure yields {"ok": false, "error": ...} without failing the whole report, that ambiguous names surface all candidates rather than guessing, and that full=True costs one section sweep per child. These are non-obvious behavioral traits well past what the annotations convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core promise, then well-sectioned bullets. It is somewhat long, with the discovery-first logic restated in both the body and the args list, but given five undocumented parameters and a complex composite operation the length is largely earned.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex composite tool with no output schema and 0% schema coverage, the description supplies parameter meaning, routing rules, partial-failure semantics, and cross-school scoping. No output schema exists so return-value detail is not owed, and nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage the description must carry the full burden, and it does: date_str format (YYYY-MM-DD, default today), name (first/last/full, ambiguity behavior, ignored when student_id is set), student_id (preferred person_id, cross-school unless scoped), subdomain (default active), and full (parent-only behavior and cost). All five parameters get semantics the bare schema lacks.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('One-call daily school report for a date') and enumerates exactly what it aggregates (timetable, grades, meals, absences, events). It also distinguishes itself from siblings by explaining it composes the individual section tools like get_grades and get_meals, so an agent can tell it apart without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use framing ('what happened yesterday at school' / 'what's coming tomorrow') and the alternative it replaces ('you don't need to fire 8-10 calls'). It then spells out the conditional routing: name/student_id present → specific student; omitted → discovery-first index for parents; full=True for the full multi-child report; omitted as student/teacher → logged-in account.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_gradesA
Read-onlyIdempotent

Get grades for the logged-in student. Read-only.

Args: year: School-year start year to filter by (e.g. 2025 for 2025/26). term: 'FIRST' or 'SECOND' to restrict the term. subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'subdomain', 'grades': [serialized grades with subject, teacher, percent, ...]}.

Notes: - When both year and term are omitted returns the current gradebook. - Use get_school_year to resolve the current school-year start.

ParametersJSON Schema
NameRequiredDescriptionDefault
termNo
yearNo
subdomainNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive/openWorld, so 'Read-only' is redundant. However the description adds genuine behavioral context beyond annotations: omitting year+term yields the current gradebook, and subdomain defaults to the active subdomain.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Args/Returns/Notes sections are front-loaded with purpose first and every line conveys usable information. Minor redundancy in restating 'Read-only' that annotations already cover, but no padding overall.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return shape ({'subdomain', 'grades': [...] with subject, teacher, percent}), documents defaults, and cross-references get_school_year. An agent has everything needed to invoke and interpret the call.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does: it explains `year` semantics with a concrete example (2025 for 2025/26), supplies the enum-like `term` values ('FIRST' or 'SECOND'), and documents `subdomain`'s default. Nothing about the three params is left ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb and resource scoped to the logged-in student ('Get grades for the logged-in student'), which cleanly separates it from the timetable, meal, and roster siblings. Scope (own grades, not arbitrary students) is explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It tells the agent the default behavior ('When both `year` and `term` are omitted returns the current gradebook') and routes to a sibling ('Use `get_school_year` to resolve the current school-year start'). There is no explicit when-not-to-use, but the selection logic is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_homework_materialA
Read-onlyIdempotent

Full body text and attachment list of one homework/assignment material. Read-only.

Fetches the school's material-player page for superid and flattens its widget tree into plain text plus absolute attachment URLs — the timeline notification carries only a short summary, never the assignment body or the attachment list, so this is the only way to read what the assignment says.

Args: superid: Material id, taken from additional_data.superid of a notification returned by get_timeline(category='homework'). subdomain: School whose session to use (defaults to the active subdomain).

Returns: dict: {'subdomain', 'superid', 'title', 'details', 'date_from', 'date_to', 'content', 'attachments'}. Each attachment is {'name', 'url'} with an absolute URL — hand url to download_attachment to save it.

Notes: - Discover ids first: get_timeline(category='homework') (or category='assignments') and read additional_data.superid. For a whole day of homework plus everything else prefer get_day_summary. - An invalid, expired or invisible id returns an actionable error, not a stack trace. - content is plain text; an enabled student upload area is rendered as '[Student answer / file upload area]'.

ParametersJSON Schema
NameRequiredDescriptionDefault
superidYes
subdomainNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds genuinely new behavioral context: invalid/expired ids yield an actionable error rather than a stack trace, and an enabled student upload area is rendered as a literal placeholder string. These are non-obvious traits not derivable from annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well front-loaded with a one-line summary before the Args/Returns/Notes blocks, and every sentence earns its place. It is somewhat verbose with a fairly long Returns enumeration, but the structure aids scanning rather than padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by spelling out the returned dict keys and the attachment shape ({'name','url'} with absolute URL). Combined with the discovery notes and error behavior, an agent has everything needed to call and use the result correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does so: `superid` is the material id sourced from `additional_data.superid` of a timeline notification, and `subdomain` selects the school session with an explicit default. Both parameters get meaning beyond their bare names.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening line states a specific verb+resource+scope ('Full body text and attachment list of one homework/assignment material') and immediately distinguishes it from the timeline sibling by noting the timeline carries only a short summary. An agent can tell exactly what it retrieves without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: discover ids via `get_timeline(category='homework')`, and prefer `get_day_summary` for a whole day of homework. It also names the downstream tool (`download_attachment`) that consumes the returned URLs, giving clear when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_mealsA
Read-onlyIdempotent

Get the meal menu for a date. Read-only. Always returns all five meal slots (breakfast, snack, lunch, afternoon_snack, dinner) — slots not published by the school are None.

Args: date_str: YYYY-MM-DD (default today). subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'date', 'subdomain', 'meals': {breakfast/snack/lunch/ afternoon_snack/dinner: menus}}. Each menu carries chooseable/ordered info usable with choose_meal / sign_off_meal.

Notes: - Tries the personal ordering endpoint first; when the school hasn't enabled it, falls back to the school's public canteen menu widget.

ParametersJSON Schema
NameRequiredDescriptionDefault
date_strNo
subdomainNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (which already declare read-only/idempotent/non-destructive), the description adds meaningful behavior: all five slots are always returned with unpublished ones set to None, and it discloses the fallback path from the personal ordering endpoint to the public canteen widget. This is exactly the kind of context the annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded single-sentence purpose, then cleanly separated Args/Returns/Notes sections. No filler; every line (including the None-for-unpublished detail) earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return shape ({date, subdomain, meals{...}}) and the note about the menu fields usable with downstream tools, so an agent knows both how to call it and what it gets back.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries the full burden and does: date_str is documented as YYYY-MM-DD defaulting to today, and subdomain as defaulting to the active subdomain. Both parameters get format and default semantics absent from the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Get the meal menu') plus the scoping dimension ('for a date'), and routes the agent to the downstream siblings that consume the result ('choose_meal' / 'sign_off_meal'). An agent can distinguish this read tool from the meal-mutation siblings without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly positions the tool as the read entry-point whose output feeds choose_meal/sign_off_meal, giving implied usage context. However it never states an explicit when-not or prerequisite (e.g., that you must fetch meals before choosing), so it stops short of a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_missing_teachersA
Read-onlyIdempotent

Get teachers missing on a date (default today). Read-only.

Args: date_str: YYYY-MM-DD (default today). subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'date', 'subdomain', 'teachers': [serialized missing teachers]}. Empty list when no teacher is missing.

Notes: - Pair with get_timetable_changes for the full substitution picture.

ParametersJSON Schema
NameRequiredDescriptionDefault
date_strNo
subdomainNo

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so 'Read-only' in the description is largely redundant. It does add real behavioral context, though: the exact return dict shape and the empty-list edge case when no teacher is missing. No permission or auth details are given.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded one-line summary followed by Args/Returns/Notes sections that map cleanly to what an agent needs. Section headers add mild length but no filler sentences; every line earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by documenting the return dict keys and the empty-result case, plus defaults for both optional params. Only missing element is any note on permissions or error behavior, which is minor for a read-only lookup.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does so well: date_str format 'YYYY-MM-DD' with default today, and subdomain defaulting to the active subdomain. Without this text the bare anyOf strings would be ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Get teachers missing on a date'), with the default date behavior clarified. It is clear what the tool returns, though it doesn't differentiate itself from siblings beyond the pairing note with get_timetable_changes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explains when this applies (default today, active subdomain) and explicitly points to get_timetable_changes for the full substitution picture. No when-not guidance, but the context is sufficiently clear for a simple read tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_my_studentsA
Read-onlyIdempotent

Get the students visible to the logged-in account. Read-only: parent accounts see their linked children (parsed from the school homepage); student/teacher accounts see classmates. Uses cached data.

Args: subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'subdomain', 'students': [{person_id, name, class_id, ...}]} usable with switch_to_student and get_student_timetable.

Notes: - Prefer get_my_students over get_roster to see your children / classmates; get_roster(roster_type='all_students') lists the whole school. - Cache is refreshed by clear_student_cache; scan_students returns the same visibility across all schools.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only, idempotent, non-destructive behavior, so the safety profile is covered. The description adds genuinely useful non-obvious traits: results are served from cache, refreshed via clear_student_cache, and visibility varies by account type (school homepage parsing). It stops short of rate limits or pagination notes, but the caching and visibility disclosure is substantial.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core purpose in the first sentence, then structures Args/Returns/Notes cleanly. The Returns block is justified because there is no output schema, though the section labels add some verbosity for a one-param tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return shape ({'subdomain', 'students': [...]}) and names downstream tools (switch_to_student, get_student_timetable) that consume the person_id. Visibility rules, caching lifecycle, and alternatives are all present, leaving nothing essential missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries the load for the single parameter. It documents 'subdomain: School to query' and its default ('defaults to the active subdomain'), which the schema's bare title/default does not explain. This is more than baseline for a 0%-coverage param.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Get the students visible to the logged-in account') and immediately scopes it by account type (parent vs student/teacher). It explicitly distinguishes itself from get_roster and scan_students, so an agent can route without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use guidance: prefer get_my_students over get_roster to see *your* children/classmates, versus get_roster(roster_type='all_students') for the whole school. It also names the maintenance siblings (clear_student_cache, scan_students), covering the alternatives and their conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_my_timetableA
Read-onlyIdempotent

Get the timetable for the logged-in user on a date. Read-only.

Args: date_str: YYYY-MM-DD (default today). subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'date', 'subdomain', 'lessons': [serialized lessons]}.

Notes: - For another student use get_student_timetable (by name/id); for a teacher/class/room on a date or a range use get_timetable. - Next week for yourself: get_next_week_timetable.

ParametersJSON Schema
NameRequiredDescriptionDefault
date_strNo
subdomainNo

TDQS

A4.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, openWorldHint=true, and destructiveHint=false. The description's 'Read-only' is redundant with those annotations, though it adds useful default behavior ('default today', 'defaults to the active subdomain') and a return structure. With annotations carrying the safety profile, the added behavioral context is modest.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action and then uses clear Args/Returns/Notes sections. It is appropriately sized for a tool with two optional parameters and three sibling alternatives, and every section carries useful information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description supplies the return dict structure despite no output schema, documents both optional parameters, and routes to all relevant sibling tools. Combined with annotations covering safety and openness, an agent has everything needed to call this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It documents both parameters: date_str as YYYY-MM-DD defaulting to today, and subdomain as the school to query defaulting to the active subdomain. It covers format and default meaning but does not describe valid subdomain values or edge cases.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Get the timetable for the logged-in user on a date') and explicitly distinguishes the tool from its siblings by scope. An agent can identify exactly which timetable variant this is without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section explicitly names alternatives and the conditions that select them: another student → get_student_timetable, teacher/class/room or date range → get_timetable, next week for yourself → get_next_week_timetable. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_next_ringing_timeA
Read-onlyIdempotent

Get the type (break/lesson) and time of the next school-bell ringing. Read-only.

Args: date_time_str: ISO datetime to search onward from (default: now). subdomain: School to query (defaults to the active subdomain).

Returns: Serialized ringing: type (break/lesson) and time.

Notes: - See get_periods for the full bell schedule.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo
date_time_strNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, openWorld, non-destructive), and the description's 'Read-only.' largely restates that. It does add non-redundant behavior: the search starts from the given datetime or now, the school defaults to the active subdomain, and the return payload is described. Missing handling for time zones/no-more-ringings keeps this from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose sentence is front-loaded, followed by tight Args/Returns/Notes blocks with no filler. Every line either documents a parameter, the return shape, or the sibling relationship.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return shape (serialized ringing with type and time), both parameter meanings and defaults, and a pointer to get_periods. It is essentially complete for a two-optional-param read tool, with only edge cases (no ringing after the given time, time-zone semantics) unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries the full burden and does so: date_time_str is documented as an ISO datetime searched onward from (default: now) and subdomain as the school to query (defaults to active subdomain). Both parameters gain meaning absent from the bare schema, though the ISO format variant/time-zone expectation is unspecified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('get the type and time of the next school-bell ringing') and enumerates the two return values (type: break/lesson, time). It also names the sibling get_periods as the source of the full schedule, so an agent can separate the two without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section explicitly routes the agent to get_periods for the complete bell schedule, which implies this tool is for the single next ringing. That is clear contextual guidance, but there is no explicit when-not condition (e.g., what happens when no further ringing exists on the given date).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_next_week_timetableA
Read-onlyIdempotent

Get the Mon-Fri timetable for next week for the logged-in user, grouped by weekday. Read-only.

Args: subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'monday', 'subdomain', 'week': [{weekday, date, lessons} x5]}.

Notes: - Weekdays are 'Po','Ut','St','Št','Pi'. - For the logged-in user on a single day use get_my_timetable; for any target (teacher/class/room/student) over a range use get_timetable with end_date.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so safety is covered. The description adds genuine value beyond them by disclosing the exact return shape and the weekday key convention ('Po','Ut','St','Št','Pi'), which is non-obvious domain behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose in the first sentence, then clearly separated Args/Returns/Notes sections. No filler; each block earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return dict structure, weekday format, and sibling routing, which is nearly everything an agent needs. Minor gaps remain (no error/permission behavior), but for a read-only single-param tool this is close to complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the parameter meaning. It explains that `subdomain` is the school to query and, importantly, that it defaults to the active subdomain — a default behavior the bare schema (default: null) does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with full scope: 'Get the Mon-Fri timetable for next week for the logged-in user, grouped by weekday.' It also implicitly distinguishes itself from the two timetable siblings that handle single-day and target/range queries.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section explicitly routes the agent: use `get_my_timetable` for the logged-in user on a single day, and `get_timetable` with `end_date` for any target over a range. When-to-use and alternatives are fully spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_periodsA
Read-onlyIdempotent

Get the bell schedule (periods with start/end times). Read-only.

Args: subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'periods': [{'starttime', 'endtime'}, ...]}.

Notes: - Combine with get_next_ringing_time for live bell timing.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, and non-destructive, so the extra 'Read-only.' line repeats structured data. But the description adds genuine value beyond annotations by disclosing the return structure ({'periods': [{'starttime','endtime'}]}) and cross-tool composition. No behavioral gaps of consequence remain.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded purpose sentence, then clearly delineated Args/Returns/Notes sections. Every line earns its place and there is no filler. Slightly more verbose than a single-line ideal but structurally clean.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description appropriately documents the return shape, and with a single optional parameter it explains the default. An agent has enough to call and consume the result correctly; only minor gaps around alternative-tool selection remain.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry parameter meaning. It does: 'subdomain: School to query (defaults to the active subdomain)' explains both the semantic (school selector) and the default behavior, which the schema only shows as default=null without explanation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with clarifying scope: 'Get the bell schedule (periods with start/end times).' The parenthetical disambiguates it from timetable siblings. It names get_next_ringing_time as a companion but doesn't explicitly differentiate from other schedule tools like get_timetable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section gives a useful composition hint ('Combine with get_next_ringing_time for live bell timing'), which implies intended usage. However, it does not state when to prefer this over alternatives like get_timetable, nor any when-not conditions. Usage is only implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_rosterA
Read-onlyIdempotent

Get a school roster: students, teachers, classes, classrooms or subjects. Read-only.

Args: roster_type: Which roster to return: - 'students' — students in the logged-in user's class (formerly get_students). - 'all_students' — a short list of all students in the school (formerly get_all_students). - 'teachers' — all teachers (formerly get_teachers). - 'classes' — all classes (formerly get_classes). - 'classrooms' — all classrooms (formerly get_classrooms). - 'subjects' — all subjects (formerly get_subjects). subdomain: School to query (defaults to the active subdomain).

Returns: dict keyed by the roster name, e.g. {'subdomain': ..., 'teachers': [...], ...}.

Notes: - For the students visible to the logged-in account (parents: their linked children; students: classmates) prefer get_my_students. - To look one student up by name use find_student. - Returned person/class ids feed get_timetable (target_type/target_id) and switch_to_student.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo
roster_typeYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds value by disclosing the return shape (a dict keyed by roster name) and how returned IDs are consumed downstream, which goes beyond the structured annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-structured with Args, Returns, and Notes sections, and front-loaded with the core purpose. The repeated 'formerly ...' annotations for each roster type add useful disambiguation but make the block slightly longer than strictly necessary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a two-parameter tool with 0% schema description coverage, no output schema, and only basic annotations, the description is complete: it documents both parameters, specifies the return format, and points to related tools for adjacent operations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry parameter meaning, and it does. roster_type is fully documented with each allowed value and its exact meaning, and subdomain is explained with its default behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Get) and resource (school roster) and enumerates the exact roster types available. It also names the sibling tools it supersedes (formerly get_students, etc.), so an agent can distinguish it from related tools without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section explicitly routes the agent to alternatives: prefer get_my_students for students visible to the logged-in account, and use find_student for name lookup. It also explains how returned IDs feed get_timetable and switch_to_student, giving clear when-to-use context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_school_yearA
Read-onlyIdempotent

Return the current school year (starting year). Read-only.

Args: subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'school_year': , 'subdomain': ...}.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so the description's 'Read-only' line is largely redundant. It does add the return shape ({'school_year': <int>, 'subdomain': ...}), which is genuinely useful since no output schema exists, but it says nothing about caching, staleness, or auth.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first line, followed by clean Args/Returns sections. Slight redundancy in restating 'Read-only' from the annotations, but overall tight and well-organized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter read tool with no output schema, the description supplies the return shape and the parameter default, which is everything an agent needs to call it and consume the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries the burden, and it does: it explains subdomain means 'School to query' and that it 'defaults to the active subdomain'. That clarifies defaulting behavior the schema's bare null default does not.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return the current school year (starting year)') and even disambiguates the value semantics by defining school year as the starting year. None of the sibling tools offer this, so an agent can route to it unambiguously.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the getter nature and the note that subdomain 'defaults to the active subdomain', but there is no explicit when-to-use or when-not-to-use guidance and no mention of alternatives. Adequate minimum-viable coverage for a simple read.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_student_timetableA
Read-onlyIdempotent

Get a student's timetable by first/last name OR person_id. Read-only. Without a subdomain, searches every school in the discovery scope (the configured EDUPAGE_SUBDOMAINS, or all logged-in schools when unset) and returns one result per school where the student is found — so a student attending multiple schools yields separate per-school timetables.

Args: name: Student's first/last/full name. student_id: person_id (preferred — unambiguous, see find_student). date_str: YYYY-MM-DD (default today). subdomain: Restrict to one school (default: all logged-in schools).

Returns: dict: {'results': [{student, student_id, class_id, date, subdomain, lessons}]}; with a query/matched_schools summary when more than one school is searched.

Notes: - If logged in as a parent this resolves the child agent-side and queries their timetable directly (no session switching). For your own timetable use get_my_timetable. - Use the student_id from find_student / get_my_students for unambiguous lookups.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNo
date_strNo
subdomainNo
student_idNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, so the safety profile is covered; the description adds material context beyond that: multi-school search scope, one result per school for multi-school students, the query/matched_schools summary, parent child-resolution without session switching, and subdomain defaulting to all logged-in schools.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and scoping caveat, and the Args/Returns/Notes structure aids scanning. It is on the long side and some notes restate preference for student_id already covered in Args, but each block still carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description compensates by describing the return dict shape and the multi-school summary variant. With no required params, the zero-parameter case is handled by documenting optional-key defaults, leaving no gap for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden — and it does: name formats, student_id as person_id with a cross-reference, date_str as YYYY-MM-DD with a today default, and subdomain semantics. Every parameter is disambiguated.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Get a student's timetable') plus the two lookup keys, and explicitly distinguishes itself from get_my_timetable and get_timetable by naming the alternative for one's own schedule. An agent can route correctly without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use rules: prefer student_id (unambiguous, sourced from find_student/get_my_students), use get_my_timetable for your own timetable, and explanatory behavior for parent logins. Alternatives and edge conditions are spelled out rather than implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_subdomainsA
Read-onlyIdempotent

List the school subdomains available to the logged-in account and the server's session status per school, plus overall login state. Read-only.

Args: None. This tool takes no arguments — the active credentials and EDUPAGE_SUBDOMAINS determine the result entirely.

Returns: dict with: - subdomains: list of school subdomains available to the account. For a logged-in parent account this is live-discovered (via edupage-api's get_subdomains, read from the profile page) and includes schools with no session yet; it is limited to EDUPAGE_SUBDOMAINS when that is set (allowlist), otherwise every accessible school is listed. For student/teacher accounts or when not logged in it falls back to the configured / current sessions. - schools: per subdomain {subdomain, logged_in, role (student/parent/teacher), user_id, two_factor_pending, active}. When EDUPAGE_SUBDOMAINS is set it contains only the in-scope schools (sessions for unconfigured schools are never listed). - active_subdomain: the school used by tools without an explicit subdomain argument. - failed_logins: subdomain → error for login attempts that failed or are blocked (e.g. pending 2FA at startup). - env_*_set: whether EDUPAGE_USERNAME / EDUPAGE_PASSWORD / EDUPAGE_SUBDOMAINS are configured.

Notes: - Use this instead of the former auth_status / user_id tools. - To connect to a discovered subdomain that has no session yet, pass it to login_all. - A school with two_factor_pending: True needs two_factor_finish.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, and the description adds real behavioral context beyond them: live-discovery via the profile page for parent accounts vs fallback for student/teacher/unauthenticated, the allowlist effect of EDUPAGE_SUBDOMAINS, and the semantics of failed_logins (blocked login attempts, pending 2FA). This is substantive disclosure, not a restatement of the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and cleanly sectioned into Args/Returns/Notes, so it is easy to scan. However the Returns block is dense and somewhat over-explained (e.g. repeated allowlist caveats in both subdomains and schools), which is more than strictly necessary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description must document the return shape, and it does so thoroughly: subdomains, per-school session fields, active_subdomain, failed_logins, and env_*_set. Given the role-dependent branching behavior, this level of detail is exactly what an agent needs to interpret results correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters means baseline 4. The description reinforces that no arguments are taken and explains that the result is determined entirely by active credentials and EDUPAGE_SUBDOMAINS, which is useful framing beyond the empty schema, but there is no parameter syntax to clarify.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'List the school subdomains available to the logged-in account and the server's session status per school.' It explicitly enumerates what is returned (subdomains, session status, login state) and points back to the tools it supersedes ('Use this instead of the former auth_status / user_id tools'). An agent can distinguish it from the login/switch siblings without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It names the alternatives and the conditions that select them: use login_all to connect to a discovered subdomain with no session, and use two_factor_finish when two_factor_pending is True. It also instructs replacement of the deprecated auth_status/user_id tools. When-to-use is explicit rather than inferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_timelineA
Read-onlyIdempotent

Get EduPage timeline notifications, filtered by category. Read-only.

Args: category: Which event types to return: - 'recent' (default) — all currently visible notifications (homework, tests, messages, grades, events...). - 'history' — all notifications since date_from (incl. older ones). - 'homework' — homework assignments (formerly get_homework). - 'assignments' — homework, tests, exams and projects (formerly get_assignments). - 'absences' — absence records (formerly get_absences). - 'events' — upcoming events: trips, excursions, meetings, holidays... (formerly get_upcoming_events). - 'news' — school news (formerly get_news). date_from: YYYY-MM-DD. Only meaningful for category='history'. subdomain: School to query (defaults to the active subdomain).

Returns: dict with subdomain and the matching notifications, e.g. {'subdomain': ..., 'notifications': [...]} (key is the category name).

Notes: - Categories are derived from timeline notifications; a school that doesn't publish a given event type returns an empty list. - For a whole-day report (timetable, substitutions, meals, homework, events, news, grades) prefer get_day_summary — one call.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryNorecent
date_fromNo
subdomainNo

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, and the description redundantly restates 'Read-only'. The added value is real behavioral context: the return shape, the keying by category name, and the fact that a school not publishing an event type returns an empty list rather than erroring. It stops short of pagination or limit behavior, so a 4 rather than a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded one-line summary, then Args/Returns/Notes blocks that each earn their place. Length is justified by the seven category values, and no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema, the description supplies the return shape and key, the empty-list edge case, per-category semantics, the date_from constraint, the subdomain default, and the sibling alternative. An agent has everything needed to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does: it documents the full enumerated set of category values with defaults, specifies date_from's YYYY-MM-DD format and its conditional relevance, and states subdomain defaults to the active subdomain. Nothing in the schema is left unexplained.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Get EduPage timeline notifications, filtered by category') and immediately differentiates the scope from siblings by naming get_day_summary as the whole-day alternative. An agent can distinguish this from get_timetable, get_grades, and the category-specific former tools without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly enumerates every category value with its meaning, marks 'recent' as the default, ties date_from to category='history' only, and gives a named alternative (get_day_summary) with the condition that selects it. When/when-not and alternatives are all present.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_timetableA
Read-onlyIdempotent

Get the timetable for a teacher, student, class or classroom on a date (or a date range, see end_date). Read-only.

Args: target_type: 'teacher' | 'student' | 'class' | 'classroom'. target_id: person/class/classroom id as returned by get_roster. date_str: Single day, YYYY-MM-DD (default today). Ignored when end_date is given. end_date: When set, returns the timetable for every day from date_str (default today) to end_date inclusive, keyed by date — the equivalent of the former get_timetable_range. subdomain: School to query (defaults to the active subdomain).

Returns: Single-day shape: {'target', 'date', 'subdomain', 'lessons'}. Range shape: {'subdomain', 'range': {: single-day result}}. Days with no published data get an empty lessons list.

Notes: - For the logged-in user's own timetable prefer get_my_timetable; for a student by name/id use get_student_timetable. - Any school's whole-week plan for yourself: get_next_week_timetable.

ParametersJSON Schema
NameRequiredDescriptionDefault
date_strNo
end_dateNo
subdomainNo
target_idYes
target_typeYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent annotations, the description discloses default behaviors (date_str defaults to today, subdomain defaults to active), precedence rules (date_str ignored when end_date is set), range semantics, and edge-case handling (days with no data return an empty lessons list). It also maps the tool to the former get_timetable_range.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, then organized into Args/Returns/Notes sections with no wasted sentences. It is longer than strictly minimal (the Returns block restates shapes the caller could infer), but every part is informative and scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by documenting both return shapes and the empty-day case, and it covers all five parameters, defaults, and sibling routing. An agent has everything needed to call and interpret this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does: it enumerates target_type's allowed values, states target_id's provenance (as returned by get_roster), documents date_str's format/default and its precedence against end_date, explains end_date's inclusive range keying, and notes subdomain's default.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Get) and resource (timetable) plus the exact target scope (teacher, student, class, classroom) and temporal scope (date or range). It is immediately distinguishable from siblings like get_my_timetable and get_student_timetable, which it names.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes section explicitly routes the agent: prefer get_my_timetable for the logged-in user, get_student_timetable for a student by name/id, and get_next_week_timetable for a whole-week plan. Conditions and alternatives are stated, not implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_timetable_changesA
Read-onlyIdempotent

Get substitution/timetable changes for a date (default today). Read-only.

Args: date_str: YYYY-MM-DD (default today). subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'date', 'subdomain', 'changes': [serialized substitutions]}. Empty list when nothing changed or the school publishes none.

Notes: - Pair with get_missing_teachers for the full substitution picture. - For one student's plan on a day use get_student_timetable / get_timetable.

ParametersJSON Schema
NameRequiredDescriptionDefault
date_strNo
subdomainNo

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint/idempotentHint/destructiveHint=false, so the leading 'Read-only.' mostly restates structured data. The description does add real behavior beyond annotations: the today/active-subdomain defaults and the fact that an empty list is the normal no-change signal, which prevents the agent from misreading an empty response as an error.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded one-line summary followed by clean Args/Returns/Notes sections, with no wasted sentences. Minor redundancy between the summary's 'Read-only' and the annotations, but every block earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return shape ({'date','subdomain','changes': [...]}) and the empty-list case, plus the sibling routing an agent needs. Nothing required to call or interpret this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does so: date_str is documented as YYYY-MM-DD with a today default, and subdomain as the school to query defaulting to the active subdomain. Both of the two params gain format and default semantics absent from the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence names a specific verb ('Get') and resource ('substitution/timetable changes') plus the scope ('for a date, default today'), and the Notes distinguish it from get_student_timetable / get_timetable. An agent can tell it apart from the other timetable siblings without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Notes explicitly route the agent: pair with `get_missing_teachers` for the full picture, and use `get_student_timetable` / `get_timetable` for a single student's plan. Both the alternative and the condition that selects it are stated, leaving nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

loginA
Idempotent

Log in to Edupage for a school. Writes: establishes (or replaces) the server-side session for that subdomain.

Args: username: EduPage account login. Falls back to EDUPAGE_USERNAME. password: Account password. Falls back to EDUPAGE_PASSWORD. subdomain: School subdomain (e.g. 'school'). Falls back to the first value of EDUPAGE_SUBDOMAINS; for method='auto' it is optional and tags the detected school's session. method: How to authenticate: - 'credentials' (default) — username/password for a known subdomain. - 'auto' — portal auto-detect of the school (formerly login_auto). - 'session' — build a session from an existing PHPSESSID cookie (formerly login_from_session); pass it in session_id. session_id: PHPSESSID cookie value, required when method='session'.

Returns: dict with logged_in status, subdomain, user_id, role and whether 2FA is pending. When two_factor_required is true, finish with two_factor_finish.

Notes: - Each login call adds/replaces that subdomain's session; call login_all to log into several schools in one call. - When EDUPAGE_SUBDOMAINS is set it is a strict allowlist: login into a school outside it is refused. - Prefer setting EDUPAGE_USERNAME / EDUPAGE_PASSWORD (and EDUPAGE_SUBDOMAINS for multi-school) — the server then logs in automatically at startup.

ParametersJSON Schema
NameRequiredDescriptionDefault
methodNocredentials
passwordNo
usernameNo
subdomainNo
session_idNo

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover the safety profile (readOnly=false, idempotent=true). The description goes well beyond, disclosing that each call adds/replaces a subdomain's server-side session, that EDUPAGE_SUBDOMAINS acts as a strict allowlist with refusals, and that 2FA may need two_factor_finish to complete.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the one-line purpose and write semantics, then cleanly sectioned into Args/Returns/Notes. Despite its length, every line conveys non-derivable information (env fallbacks, allowlist behavior, 2FA handoff) rather than restating the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Although there is no output schema, the description documents the return dict (logged_in, subdomain, user_id, role, 2FA-pending) and the follow-up tool for 2FA. For an auth tool with five optional params and no required fields, this is complete enough to invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage the description must carry the full burden, and it does: all five params are documented, including env-var fallbacks for username/password/subdomain and the required session_id value when method='session'. The method enum values are fully explained even though the schema declares no enum.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ("Log in to Edupage for a school") and immediately scopes the side effect ("establishes (or replaces) the server-side session for that subdomain"). It clearly differentiates from the sibling login_all by noting that tool handles multiple schools in one call.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly enumerates the three mutually exclusive authentication strategies ('credentials', 'auto', 'session') with the condition that selects each, and names login_all as the alternative for multi-school login. It also advises preferring EDUPAGE_USERNAME/EDUPAGE_PASSWORD for auto-login at startup.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

login_allA
Idempotent

Log in to one or more schools in a single call. Writes: establishes (or replaces) the server-side session for each subdomain.

Args: subdomains: Comma-separated school subdomains, e.g. 'school1,school2'. Falls back to EDUPAGE_SUBDOMAINS. usernames: Comma-separated usernames, one per school (or a single one). Falls back to EDUPAGE_USERNAME. passwords: Comma-separated passwords, one per school (or a single one). Falls back to EDUPAGE_PASSWORD.

Returns: dict: {'results': [{subdomain, ok, user_id, role, two_factor_required}], 'active_subdomain': ...}. A failed school is reported per-entry with its error.

Notes: - Add schools one at a time with login. - When EDUPAGE_SUBDOMAINS is set it is a strict allowlist: schools outside it are refused per-entry without creating a session. - Finish any pending 2FA with two_factor_finish.

ParametersJSON Schema
NameRequiredDescriptionDefault
passwordsNo
usernamesNo
subdomainsNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnlyHint=false, idempotentHint=true, destructiveHint=false), yet the description adds real behavioral context beyond them: it discloses that sessions are established OR REPLACED per subdomain, that EDUPAGE_SUBDOMAINS acts as a strict allowlist with per-entry refusal, and that failures are reported per-entry rather than aborting the call.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with a one-sentence purpose, then organized into Args/Returns/Notes sections where each block is functional. It is somewhat long, but the length is driven by needed fallback and failure semantics rather than padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return shape ({'results': [{subdomain, ok, user_id, role, two_factor_required}], 'active_subdomain': ...}) plus per-entry error handling, allowlist behavior, and required follow-up calls. Nothing an agent needs in order to invoke or interpret this tool is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does so thoroughly: it documents the comma-separated format for all three params, the positional alignment rule ('one per school (or a single one)'), and the environment-variable fallback for each (EDUPAGE_SUBDOMAINS/USERNAME/PASSWORD).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Log in to one or more schools in a single call') and immediately declares the write nature of the operation. The batch/multi-school scope is explicit, which is what separates it from the single-school sibling `login`.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Notes route the agent to the right alternatives: `login` for adding schools one at a time and `two_factor_finish` for completing pending 2FA. However, it never states the inverse condition explicitly (e.g. 'prefer login when only one school is needed'), so the routing is implied rather than fully spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rate_mealB
DestructiveIdempotent

Rate a meal. Writes: submits quality/quantity ratings for a date and meal type.

Args: date_str: YYYY-MM-DD of the meal. meal_type: 'snack' | 'lunch' | 'afternoon_snack'. quality: Taste rating, 1-5. quantity: Portion-size rating, 1-5. subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'rated': True, meal_type, date}.

ParametersJSON Schema
NameRequiredDescriptionDefault
qualityYes
date_strYes
quantityYes
meal_typeYes
subdomainNo

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the write/destructive/idempotent nature, and the description adds the return format and parameter details. However, it does not elaborate on what 'destructive' means (e.g., overwriting an existing rating) or confirm idempotency, so it adds only modest context beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with a clear summary and then structured into Args and Returns sections. It is appropriately sized for a 5-parameter tool with no schema descriptions, though 'Writes:' is slightly redundant with 'Rate a meal'.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description provides the return shape, and it documents all parameters thoroughly. It could mention overwrite behavior or permissions, but with annotations covering safety and a clear action, it is largely complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden. It defines all five parameters with format and meaning: date_str as YYYY-MM-DD, meal_type with enumerated values, quality and quantity as 1-5 scales, and subdomain defaulting to the active subdomain. This is thorough.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Rate a meal' and clarifies it writes quality/quantity ratings for a date and meal type. It does not explicitly differentiate from sibling tools like choose_meal or sign_off_meal, but the purpose is clear.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no guidance on when to use this tool versus alternatives such as get_meals, choose_meal, or sign_off_meal. The description only implies usage through the name and action, leaving the agent to infer.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scan_studentsA
Read-onlyIdempotent

Discover all students visible to the logged-in account across the discovery scope (the configured EDUPAGE_SUBDOMAINS, or every school when unset). Read-only; uses cached data to avoid redundant API calls.

Args: None. This tool takes no arguments — the discovery scope is fixed by EDUPAGE_SUBDOMAINS and the logged-in role, both of which are environment/login state rather than per-call input.

Returns: dict: {'students': [{name, student_id, class_id, subdomain}], 'total': n}. For a parent account: their linked children in each school; for a student/teacher: classmates. One entry per student per school.

Notes: - get_my_students returns the same view for the active subdomain; clear_student_cache refreshes it.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so safety is covered structurally. The description adds real value beyond them: it uses cached data to avoid redundant API calls, and it clarifies the return shape and how results differ for parent vs student/teacher accounts (one entry per student per school).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then structured Args/Returns/Notes sections; every block earns its place. It is slightly longer than strictly necessary given the Notes section repeats the sibling relationship, but there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the return-format burden and does so explicitly (dict with students list of name/student_id/class_id/subdomain and a total). Scope, caching behavior, and account-dependent return semantics are all covered, so an agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, and the description goes beyond the empty schema by explaining WHY there are no args — scope comes from EDUPAGE_SUBDOMAINS and login role, both environment/login state rather than per-call input. That is exactly the justification an agent needs to avoid hunting for nonexistent inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (discover/scan) and resource (students), plus the precise scope: all students visible across the configured EDUPAGE_SUBDOMAINS discovery scope. It explicitly differentiates from the sibling get_my_students by noting that tool returns only the active subdomain, so an agent can distinguish the two without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear usage context: read-only, scope fixed by environment/role, and names the alternatives get_my_students (same view for active subdomain) and clear_student_cache (refreshes). It does not spell out an explicit when-not-use rule, but the contrast with get_my_students effectively signals the selection condition.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

send_messageA
Destructive

Send a message to a recipient. Writes: posts a new message on the recipient's timeline.

Args: recipient_id: EduPage id like 'Student123' or 'Teacher456' (see get_roster). body: Message text. Must not be empty. subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'sent': True, 'timeline_id': }.

Notes: - Recipient ids come from get_roster(roster_type='students'|'teachers') or get_my_students.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyYes
subdomainNo
recipient_idYes

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false, destructiveHint=true, idempotentHint=false and openWorldHint=true, so the safety profile is covered. The description adds the destination of the write (recipient's timeline) and the non-empty body constraint, but says nothing about irreversibility, visibility to the recipient, or rate limits for a destructive write.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the action and its side effect, then uses tidy Args/Returns/Notes sections where each line carries information. Slightly verbose in structure, but no sentence is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With only three parameters, no output schema, and no enums, the description covers what an agent needs: all three arguments, the return shape ({'sent': True, 'timeline_id': <id>}), and where ids originate. Only the absence of failure/error behavior keeps it from being fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it largely does: it gives the recipient_id format and concrete examples ('Student123'/'Teacher456') plus its source tool, states body must not be empty, and explains that subdomain defaults to the active subdomain.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ("Send a message to a recipient") and immediately clarifies the effect: it posts onto the recipient's timeline. This cleanly separates it from read-oriented siblings like get_timeline and get_roster, so an agent knows it is a write tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: the Notes tell the agent where recipient ids come from (get_roster, get_my_students), which is a useful precondition, but there is no explicit statement of when to use this tool versus alternatives or when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sign_off_mealA
DestructiveIdempotent

Cancel an ordered meal for a date. Writes: releases the booking.

Args: date_str: YYYY-MM-DD to cancel. meal_type: 'snack' | 'lunch' | 'afternoon_snack'. subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'ordered': False, meal_type, date}.

ParametersJSON Schema
NameRequiredDescriptionDefault
date_strYes
meal_typeYes
subdomainNo

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the write/destructive/idempotent/openWorld profile, so the bar is lower, and the description adds real context by stating "Writes: releases the booking" and showing the returned state {'ordered': False}. It does not mention permission/auth requirements or what happens if no booking exists, so it stops short of full disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and write semantics, followed by compact Args/Returns blocks; every line is informational. The fragment "Writes: releases the booking" is slightly telegraphic, but nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description helpfully specifies the return dict, and it documents all three parameters. The gaps are error/edge-case behavior (meal not ordered, invalid date, permission failure) rather than anything needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the schema exposes meal_type as an unconstrained string, so the description carries the full burden — and does: it supplies the YYYY-MM-DD format, the exact allowed meal_type values ('snack'|'lunch'|'afternoon_snack'), and the subdomain default behavior. This is essential information present nowhere else.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb ("Cancel") and resource ("an ordered meal"), scoped by date, which disambiguates the otherwise opaque name sign_off_meal. This reads clearly as the inverse of the sibling choose_meal, so an agent can route without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied: you cancel a meal that was previously ordered for a given date. However, there is no explicit when/when-not guidance, no statement of prerequisites (e.g., a booking must already exist), and no named alternative such as choose_meal.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

switch_to_parentA
Idempotent

Switch the session back to the parent account (parent accounts only). Writes: changes which account subsequent tools operate as.

Args: subdomain: School whose session to restore (defaults to the active).

Returns: dict: {'switched_to_parent': True, 'user_id': ...}.

Notes: - Pair with switch_to_student; only relevant after a parent session was switched to a child.

ParametersJSON Schema
NameRequiredDescriptionDefault
subdomainNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly=false and idempotent=true, but the description adds meaningful context beyond them: the write's actual effect ('changes which account subsequent tools operate as') and the return payload. It doesn't cover failure modes (e.g., calling when not switched), which keeps it short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-structured with labeled sections (purpose, Writes, Args, Returns, Notes) and the core purpose front-loaded. Slightly longer than strictly necessary, but each section adds usable information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the 'Returns' section is genuinely useful. The description covers the write effect, the sibling relationship, the argument, and the return shape. Only error/precondition-failure behavior is left unspecified.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the param. It explains the single 'subdomain' arg as 'School whose session to restore' and its default behavior ('defaults to the active'), matching the null default. Clear and sufficient for one parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Switch the session back to the parent account') with a clear scope qualifier ('parent accounts only'). An agent can distinguish it from switch_to_student without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the paired alternative ('Pair with `switch_to_student`') and the precondition that selects it ('only relevant after a parent session was switched to a child'). The 'parent accounts only' qualifier also acts as an exclusion, leaving little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

switch_to_studentA
Idempotent

Switch the session to a student account (parent accounts only). Writes: changes which account subsequent tools operate as.

Args: student_id: person_id of the child (from get_my_students). name: first/last/full name of the child, used when student_id is omitted. subdomain: School to query (defaults to the active subdomain).

Returns: dict: {'switched_to_student': , 'user_id': ...}.

Notes: - Revert with switch_to_parent. Prefer the stateless get_student_timetable (name/student_id) over switching when you only need a timetable.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNo
subdomainNo
student_idNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Description discloses the key behavioral trait — this is a stateful write that changes which account subsequent tools operate as — plus the revert path. It also explains the student_id vs name fallback and the subdomain default. Annotations (idempotentHint=true, destructiveHint=false) already cover safety, so remaining gaps are minor.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded purpose in the first clause, then Args/Returns/Notes sections. Slightly verbose with the Returns dict, but the structure is clear and every section earns its place for a stateful tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description supplies the return shape, the stateful side effect, the sibling alternatives, the revert path, and parameter fallback logic. An agent has everything needed to call this correctly without opening other tools' docs.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry the load — and it does, explaining student_id (person_id from get_my_students), name as fallback when student_id is omitted, and subdomain defaulting to the active subdomain. Uses of name (first/last/full) are clarified as the child's name, though the accepted string format isn't fully pinned down.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific action (switch session to a student account) with the precondition (parent accounts only). Distinguishes itself from siblings like get_student_timetable and switch_to_parent by clarifying it changes the operating account context for subsequent tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use it (parent accounts switching to a child) and when not to (prefer stateless get_student_timetable for timetable-only needs), and names switch_to_parent as the revert. This is exactly the routing guidance an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

two_factor_finishA
Idempotent

Finish a pending 2FA login. Writes: completes the pending auth flow. Only needed after a login that returned two_factor_required: True.

Args: code: Optional email/app verification code. When given it is used directly; otherwise the device-confirmation flow is polled. subdomain: School subdomain whose pending login to complete (defaults to the active subdomain). poll_seconds: How long (seconds) to wait for approval on a device when code is not given. Defaults to 60; on timeout the caller can call two_factor_finish again later.

Returns: dict: {'confirmed': True, 'logged_in': True, subdomain, user_id, role} on success, or a pending status when the confirmation wasn't approved within the poll window.

ParametersJSON Schema
NameRequiredDescriptionDefault
codeNo
subdomainNo
poll_secondsNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes beyond the annotations by disclosing the pending-auth completion semantics, the code-vs-poll branching, the poll timeout fallback ('the caller can call `two_factor_finish` again later'), and the concrete success return shape. The write/idempotent profile in annotations is consistent with, and supplemented by, this detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and precondition, then organized into Args/Returns sections, so it is easy to scan. Slight redundancy between the title sentence and 'Writes: completes the pending auth flow' keeps it short of maximal conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-param auth tool with no output schema and sparse annotations, the description supplies the precondition, per-parameter semantics, branching logic, timeout handling, and an explicit return contract. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description carries the full burden and does: `code` is explained as a directly-used verification code that switches behavior, `subdomain` defaults to the active subdomain, and `poll_seconds` defaults to 60 and only matters when `code` is absent. Every parameter's meaning and interaction is covered.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Finish a pending 2FA login') and immediately clarifies the write nature of the operation. It is clearly distinguishable from the sibling `login` tool by its focus on the post-login confirmation step.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states the triggering condition: 'Only needed after a `login` that returned `two_factor_required: True`.' It also explains the fallback path when no code is supplied and that the tool may be re-invoked after a poll timeout, covering when and how to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 30 tool updatesv0.6.0
    • Changedchoose_meal2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedclear_student_cache2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedcustom_request6 fields changed
      • addedInput schema / properties / data / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / data / type
        Removed value: -"string"
      • addedInput schema / properties / headers / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / headers / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Addeddownload_attachment
    • Removeddownload_homework_file
    • Changedfind_student2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_day_summary10 fields changed
      • addedInput schema / properties / date_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_str / type
        Removed value: -"string"
      • addedInput schema / properties / full / anyOf
        Added value: +[
        +  {
        +    "type": "boolean"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / full / type
        Removed value: -"boolean"
      • addedInput schema / properties / name / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / name / type
        Removed value: -"string"
      • addedInput schema / properties / student_id / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / student_id / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_grades6 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
      • addedInput schema / properties / term / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / term / type
        Removed value: -"string"
      • addedInput schema / properties / year / anyOf
        Added value: +[
        +  {
        +    "type": "integer"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / year / type
        Removed value: -"integer"
    • Changedget_homework_material2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_meals4 fields changed
      • addedInput schema / properties / date_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_str / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_missing_teachers4 fields changed
      • addedInput schema / properties / date_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_str / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_my_students2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_my_timetable4 fields changed
      • addedInput schema / properties / date_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_str / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_next_ringing_time4 fields changed
      • addedInput schema / properties / date_time_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_time_str / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_next_week_timetable2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_periods2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_roster2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_school_year2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_student_timetable8 fields changed
      • addedInput schema / properties / date_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_str / type
        Removed value: -"string"
      • addedInput schema / properties / name / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / name / type
        Removed value: -"string"
      • addedInput schema / properties / student_id / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / student_id / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_timeline6 fields changed
      • addedInput schema / properties / category / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / category / type
        Removed value: -"string"
      • addedInput schema / properties / date_from / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_from / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_timetable6 fields changed
      • addedInput schema / properties / date_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_str / type
        Removed value: -"string"
      • addedInput schema / properties / end_date / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / end_date / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedget_timetable_changes4 fields changed
      • addedInput schema / properties / date_str / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / date_str / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedlogin10 fields changed
      • addedInput schema / properties / method / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / method / type
        Removed value: -"string"
      • addedInput schema / properties / password / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / password / type
        Removed value: -"string"
      • addedInput schema / properties / session_id / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / session_id / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
      • addedInput schema / properties / username / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / username / type
        Removed value: -"string"
    • Changedlogin_all6 fields changed
      • addedInput schema / properties / passwords / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / passwords / type
        Removed value: -"string"
      • addedInput schema / properties / subdomains / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomains / type
        Removed value: -"string"
      • addedInput schema / properties / usernames / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / usernames / type
        Removed value: -"string"
    • Changedrate_meal2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedsend_message2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedsign_off_meal2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedswitch_to_parent2 fields changed
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedswitch_to_student6 fields changed
      • addedInput schema / properties / name / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / name / type
        Removed value: -"string"
      • addedInput schema / properties / student_id / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / student_id / type
        Removed value: -"string"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
    • Changedtwo_factor_finish6 fields changed
      • addedInput schema / properties / code / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / code / type
        Removed value: -"string"
      • addedInput schema / properties / poll_seconds / anyOf
        Added value: +[
        +  {
        +    "type": "integer"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / poll_seconds / type
        Removed value: -"integer"
      • addedInput schema / properties / subdomain / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedInput schema / properties / subdomain / type
        Removed value: -"string"
  2. 2 tool updatesv0.5.2
    • Addeddownload_homework_file
    • Addedget_homework_material
  3. 2 tool updatesv0.5.1
    • Removedget_schools
    • Addedget_subdomains
  4. 27 tool updatesv0.5.0
    • Removedauth_status
    • Removedget_absences
    • Removedget_all_students
    • Removedget_assignments
    • Removedget_classes
    • Removedget_classrooms
    • Removedget_homework
    • Changedget_meals2 fields changed
      • removedInput schema / properties / include_breakfast
        Removed value: -{
        -  "default": false,
        -  "title": "Include Breakfast",
        -  "type": "boolean"
        -}
      • removedInput schema / properties / include_dinner
        Removed value: -{
        -  "default": false,
        -  "title": "Include Dinner",
        -  "type": "boolean"
        -}
    • Removedget_news
    • Removedget_notification_history
    • Removedget_notifications
    • Addedget_roster
    • Addedget_school_year
    • Removedget_students
    • Removedget_subjects
    • Removedget_teachers
    • Addedget_timeline
    • Changedget_timetable1 field changed
      • addedInput schema / properties / end_date
        Added value: +{
        +  "default": null,
        +  "title": "End Date",
        +  "type": "string"
        +}
    • Removedget_timetable_range
    • Removedget_upcoming_events
    • Changedlogin2 fields changed
      • addedInput schema / properties / method
        Added value: +{
        +  "default": "credentials",
        +  "title": "Method",
        +  "type": "string"
        +}
      • addedInput schema / properties / session_id
        Added value: +{
        +  "default": null,
        +  "title": "Session Id",
        +  "type": "string"
        +}
    • Removedlogin_auto
    • Removedlogin_from_session
    • Removedschool_year
    • Removedtwo_factor_check_confirmed
    • Changedtwo_factor_finish1 field changed
      • addedInput schema / properties / poll_seconds
        Added value: +{
        +  "default": 60,
        +  "title": "Poll Seconds",
        +  "type": "integer"
        +}
    • Removeduser_id
  5. 1 tool updatev0.4.10
    • Changedget_day_summary1 field changed
      • addedInput schema / properties / full
        Added value: +{
        +  "default": false,
        +  "title": "Full",
        +  "type": "boolean"
        +}
  6. 46 tool updatesv0.4.6
    • First observedauth_status
    • First observedchoose_meal
    • First observedclear_student_cache
    • First observedcustom_request
    • First observedfind_student
    • First observedget_absences
    • First observedget_all_students
    • First observedget_assignments
    • First observedget_classes
    • First observedget_classrooms
    • First observedget_day_summary
    • First observedget_grades
    • First observedget_homework
    • First observedget_meals
    • First observedget_missing_teachers
    • First observedget_my_students
    • First observedget_my_timetable
    • First observedget_news
    • First observedget_next_ringing_time
    • First observedget_next_week_timetable
    • First observedget_notification_history
    • First observedget_notifications
    • First observedget_periods
    • First observedget_schools
    • First observedget_student_timetable
    • First observedget_students
    • First observedget_subjects
    • First observedget_teachers
    • First observedget_timetable
    • First observedget_timetable_changes
    • First observedget_timetable_range
    • First observedget_upcoming_events
    • First observedlogin
    • First observedlogin_all
    • First observedlogin_auto
    • First observedlogin_from_session
    • First observedrate_meal
    • First observedscan_students
    • First observedschool_year
    • First observedsend_message
    • First observedsign_off_meal
    • First observedswitch_to_parent
    • First observedswitch_to_student
    • First observedtwo_factor_check_confirmed
    • First observedtwo_factor_finish
    • First observeduser_id

TDQS

A4.1/5.0

Scored across 31 tools

Disambiguation4/5

Most tools have clearly distinct purposes, and descriptions actively cross-reference siblings to disambiguate. Overlap exists in the timetable family (get_my_timetable/get_student_timetable/get_timetable/get_next_week_timetable) and the student-lookup family (find_student/get_my_students/scan_students/get_roster), which could cause occasional misselection despite the clarifying notes.

Naming Consistency5/5

Names follow a highly consistent snake_case verb_noun (or verb) pattern throughout: get_*, login, login_all, switch_to_*, choose_meal, rate_meal, send_message, find_student. No mixing of camelCase or inconsistent verb styles.

Tool Count3/5

At 31 tools the surface is heavy for a single-school portal client, and several clusters (four timetable tools, four student-lookup tools, three meal-write tools) could plausibly be consolidated. The domain is broad enough that the count is defensible, but it sits at the borderline-heavy end.

Completeness4/5

Strong coverage across auth (login/2FA/session switch), timetables, grades, meals (read/order/cancel/rate), messages, rosters, substitutions, homework material, attachments, and a raw custom_request escape hatch. Minor gaps remain (e.g., no inbox/read-message or event-detail tools), but core workflows are fully closed.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers