EduPage MCP Server
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| MCP_HOST | No | Bind host for HTTP transport (default localhost) | |
| MCP_PORT | No | Bind port for HTTP transport (default 8000) | |
| MCP_API_KEY | No | Optional bearer token for HTTP transport auth | |
| MCP_TRANSPORT | No | Transport mode for the MCP server | stdio |
| EDUPAGE_PASSWORD | Yes | EduPage password | |
| EDUPAGE_USERNAME | Yes | EduPage username or login | |
| EDUPAGE_SUBDOMAINS | No | Comma-separated list of school subdomains for multi-school auto-login (optional) |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| loginA | 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 Returns:
dict with logged_in status, subdomain, user_id, role and whether 2FA
is pending. When Notes:
- Each |
| login_allA | 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 |
| two_factor_finishA | Finish a pending 2FA login. Writes: completes the pending auth flow.
Only needed after a 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
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. |
| get_school_yearA | Return the current school year (starting year). Read-only. Args: subdomain: School to query (defaults to the active subdomain). Returns: dict: {'school_year': , 'subdomain': ...}. |
| get_my_timetableA | 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_timetableA | Get a student's timetable by first/last name OR person_id. Read-only.
Without a Args:
name: Student's first/last/full name.
student_id: person_id (preferred — unambiguous, see Returns:
dict: {'results': [{student, student_id, class_id, date, subdomain,
lessons}]}; with a 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_timetableA | Get the timetable for a teacher, student, class or classroom on a date
(or a date range, see Args:
target_type: 'teacher' | 'student' | 'class' | 'classroom'.
target_id: person/class/classroom id as returned by 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_next_ringing_timeA | 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_next_week_timetableA | 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_periodsA | 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_gradesA | 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 |
| get_timelineA | 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 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_homework_materialA | Full body text and attachment list of one homework/assignment material. Read-only. Fetches the school's material-player page for Args:
superid: Material id, taken from Returns:
dict: {'subdomain', 'superid', 'title', 'details', 'date_from',
'date_to', 'content', 'attachments'}. Each attachment is
{'name', 'url'} with an absolute URL — hand Notes:
- Discover ids first: |
| download_attachmentA | 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 Args:
url: Absolute attachment URL, or a school-relative path such as
'/elearning/ruqjzfpv?z%3A…' — the exact form
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 |
| get_timetable_changesA | 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_teachersA | 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_mealsA | 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 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 Notes: - Tries the personal ordering endpoint first; when the school hasn't enabled it, falls back to the school's public canteen menu widget. |
| choose_mealA | 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 Returns: dict: {'ordered': True, meal_type, date, number}. Notes:
- Read |
| sign_off_mealA | 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}. |
| rate_mealB | 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}. |
| get_day_summaryA | 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".
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
|
| get_rosterA | 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 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 |
| send_messageA | 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
Returns: dict: {'sent': True, 'timeline_id': }. Notes:
- Recipient ids come from |
| get_my_studentsA | 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 Notes:
- Prefer |
| switch_to_studentA | 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 Returns: dict: {'switched_to_student': , 'user_id': ...}. Notes:
- Revert with |
| find_studentA | Look up a student by first/last/full name using tiered matching. Read-only.
Without a 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 Notes:
- Use a |
| get_subdomainsA | 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
Returns:
dict with:
- Notes:
- Use this instead of the former |
| clear_student_cacheA | 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 Args: subdomain: School whose cache to clear. Without it, clears ALL schools. Returns: dict: {'cleared': <subdomain|'all'>, 'entries_removed': }. |
| scan_studentsA | Discover all students visible to the logged-in account across the discovery
scope (the configured Args:
None. This tool takes no arguments — the discovery scope is fixed by
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:
- |
| switch_to_parentA | 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 |
| custom_requestA | 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 Returns:
dict: {'status_code': int, 'content_type': str, 'bytes': int,
'text': str, 'truncated': bool}. 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 |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 31 tools
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.
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.
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.
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.