EduPage MCP Server
PYPI · EDUPAGE-MCP-FULL
EduPage MCP: timetables, grades, homework, meals, messages, 2FA, multi-school, role-aware.
Available components
Last scanned (1 day ago)
Scores higher than 94% of PyPI servers
Category Education and Research
How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score → Why this is hard to score →
Supply Chain Security100
- No malware found by supply-chain analysis.Pass
- No known CVEs affecting this package version or its production dependencies.Pass
- Runs setuptools.build_meta at install time, a recognised build step with no custom scripting around it. View diagnostics → Pass
- 0 of 32 dependencies flagged as unhealthy. View diagnostics → Pass
Provenance & Transparency100
- Source repository is publicly reachable at the declared URL. View diagnostics → Pass
- Cryptographically verified build provenance (signed, bound to oliverhruby/edupage-mcp). View diagnostics → Pass
- Clear OSI-approved license (MIT).Pass
- Actively maintained (last published 22 days ago).Pass
- Publishes a security disclosure policy (SECURITY.md).Pass
Schema Quality & AI Usability67
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 7228 tokens (~233/item across 31 items; 31 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management97
- Stability check failed: the tool surface changed between 0.5.3 and 0.7.0: 1 tool removals, 0 breaking changes, 1 additions. See how to fix → Fail
Tool Coverage67
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 0% of tool parameters carry a description.Fail
Tool Safety100
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- All 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.Pass
- An AI judge read all 31 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 4 Nov 26 +7
- Security disclosure: unverified → pass ▲ functional
- 3 Nov 26 −1
- Security disclosure: pass → unverified ▼ functional
- 1 Nov 26 +1
- Security disclosure: unverified → pass ▲ functional
- 31 Oct 26 −1
- Security disclosure: pass → unverified ▼ functional
- 29 Oct 26 +15
- Malware scan: unverified → pass ▲ security
- 25 Oct 26 −15
- Malware scan: pass → unverified ▼ security
- 23 Oct 26 +15
- Malware scan: unverified → pass ▲ security
- 22 Oct 26 −15
- Malware scan: pass → unverified ▼ security
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 4 Nov 2026 · Analysed pypi/edupage-mcp-full@0.7.0
Provenance Verified
A signed build attestation was found and verified, binding this exact artifact to the source repository it claims to come from.
| Result | Verified |
|---|---|
| Ecosystem | pypi |
| Reason | Verified |
| Discovered via | Registry attestation endpoint |
| Source repo | oliverhruby/edupage-mcp |
| Certificate issuer | https://token.actions.githubusercontent.com |
| Certificate SAN | https://github.com/oliverhruby/edupage-mcp/.github/workflows/publish.yml@refs/tags/v0.7.0 |
| Rekor log index | 3234286567 |
| Predicate type | PyPI publish attestation https://docs.pypi.org/attestations/publish/v1 |
| Subject digest | sha256:c5b77202f2859299d40b50c230994ac642f398d58c51de8fc69ce570b41cc1b8 |
Background: How many MCP packages publish verified provenance →
Install scripts 1 script
| Hook | Tier | Command |
|---|---|---|
| build_backend | allowlisted | setuptools.build_meta |
Background: Why install scripts are a supply-chain risk →
Dependencies 32 packages
| Packages resolved | 32 |
|---|---|
| Tree resolution | Complete |
Background: SBOMs and build attestations, explained →
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
choose_meal ~174
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`.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | string | yes | – |
| meal_type | string | yes | – |
| number | integer | yes | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
clear_student_cache ~111
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': <int>}.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
No output schema declared.
No examples provided.
custom_request ~399
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`.
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | – | – |
| headers | – | – | – |
| method | string | yes | – |
| subdomain | – | – | – |
| url | string | yes | – |
No output schema declared.
No examples provided.
download_attachment ~544
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 n…
| Name | Type | Req | Description |
|---|---|---|---|
| dest_dir | – | – | – |
| filename | – | – | – |
| subdomain | – | – | – |
| url | string | yes | – |
No output schema declared.
No examples provided.
find_student ~209
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.
| Name | Type | Req | Description |
|---|---|---|---|
| name | string | yes | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_day_summary ~423
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.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | – | – | – |
| full | – | – | – |
| name | – | – | – |
| student_id | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_grades ~155
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.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
| term | – | – | – |
| year | – | – | – |
No output schema declared.
No examples provided.
get_homework_material ~321
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]'.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
| superid | string | yes | – |
No output schema declared.
No examples provided.
get_meals ~183
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.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_missing_teachers ~111
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.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_my_students ~179
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.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_my_timetable ~140
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`.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_next_ringing_time ~109
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.
| Name | Type | Req | Description |
|---|---|---|---|
| date_time_str | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_next_week_timetable ~149
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`.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_periods ~85
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.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_roster ~281
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`.
| Name | Type | Req | Description |
|---|---|---|---|
| roster_type | string | yes | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_school_year ~63
Return the current school year (starting year). Read-only. Args: subdomain: School to query (defaults to the active subdomain). Returns: dict: {'school_year': <int>, 'subdomain': ...}.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_student_timetable ~290
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.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | – | – | – |
| name | – | – | – |
| student_id | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_subdomains ~405
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`.
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
get_timeline ~321
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.
| Name | Type | Req | Description |
|---|---|---|---|
| category | – | – | – |
| date_from | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
get_timetable ~308
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': {<YYYY-MM-DD>: 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`.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | – | – | – |
| end_date | – | – | – |
| subdomain | – | – | – |
| target_id | string | yes | – |
| target_type | string | yes | – |
No output schema declared.
No examples provided.
get_timetable_changes ~137
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`.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
login ~381
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.
| Name | Type | Req | Description |
|---|---|---|---|
| method | – | – | – |
| password | – | – | – |
| session_id | – | – | – |
| subdomain | – | – | – |
| username | – | – | – |
No output schema declared.
No examples provided.
login_all ~244
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`.
| Name | Type | Req | Description |
|---|---|---|---|
| passwords | – | – | – |
| subdomains | – | – | – |
| usernames | – | – | – |
No output schema declared.
No examples provided.
rate_meal ~595
Rate the taste and portion size of a meal. Writes: submits both scores and overwrites whatever rating this account's student already gave for that date and meal type. One rating per (date, meal_type), filed against the student the session is logged in as. It replaces the previous rating outright — no history is kept and no tool here undoes it — so the call is safe to repeat with the same scores, but changing a score means submitting again and losing the old one. Rate a meal the student actually ate. Read `get_meals` first to confirm the slot is published and to see the current averages. Not an ordering tool: `choose_meal` books a menu and `sign_off_meal` releases the booking, and neither records a rating. Only the three canteen slots can be rated — breakfast and dinner, which `get_meals` also returns, carry no rating board and fail with "No 'breakfast' meal available". Args: date_str: YYYY-MM-DD of the meal that was eaten. meal_type: 'snack' | 'lunch' | 'afternoon_snack' — the slots `choose_meal` and `sign_off_meal` also accept, and the only ones with a rating board. quality: Taste, 1 (inedible) to 5 (excellent), integer. quantity: Portion size, 1 (far too little) to 5 (far too much), integer. Submitted in the same request as `quality`; neither score can be left out or rated on its own. subdomain: School to query (defaults to the active subdomain). Returns: dict: {'rated': True, 'meal_type', 'date'}. The scores are not echoed back — re-read `get_meals` to confirm what was recorded. Notes: - Fails with "No rating available for this meal" when the school has not opened ratings for that menu, and with a "Missing boarder id" error when the session carries no canteen list to file the score against — `switch_to_student` first if one login covers several children. - The score posts to the school's canteen endpoint under that dining id and counts towards the averages `get_meals…
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | string | yes | – |
| meal_type | string | yes | – |
| quality | integer | yes | – |
| quantity | integer | yes | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
scan_students ~185
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.
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
send_message ~151
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': <id>}. Notes: - Recipient ids come from `get_roster(roster_type='students'|'teachers')` or `get_my_students`.
| Name | Type | Req | Description |
|---|---|---|---|
| body | string | yes | – |
| recipient_id | string | yes | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
sign_off_meal ~109
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}.
| Name | Type | Req | Description |
|---|---|---|---|
| date_str | string | yes | – |
| meal_type | string | yes | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
switch_to_parent ~100
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.
| Name | Type | Req | Description |
|---|---|---|---|
| subdomain | – | – | – |
No output schema declared.
No examples provided.
switch_to_student ~165
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': <person_id>, '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.
| Name | Type | Req | Description |
|---|---|---|---|
| name | – | – | – |
| student_id | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
two_factor_finish ~201
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.
| Name | Type | Req | Description |
|---|---|---|---|
| code | – | – | – |
| poll_seconds | – | – | – |
| subdomain | – | – | – |
No output schema declared.
No examples provided.
How do I install the EduPage MCP Server server?
EduPage MCP Server runs locally as a PyPI package, launched with uvx edupage-mcp-full. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
pypi · edupage-mcp-full
claude mcp add oliverhruby-edupage-mcp -- uvx edupage-mcp-full
What is the EduPage MCP Server server?
EduPage MCP Server is listed in the public MCP registry as io.github.oliverhruby/edupage-mcp. EduPage MCP: timetables, grades, homework, meals, messages, 2FA, multi-school, role-aware. This page covers its PyPI package (edupage-mcp-full).
Is the EduPage MCP Server server safe to use?
EduPage MCP Server scores 91 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 4 November 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the EduPage MCP Server server expose?
EduPage MCP Server exposes 31 tools: login, login_all, two_factor_finish, get_school_year, get_my_timetable, and 26 more. Their descriptions and schemas cost roughly 7,228 tokens of context every time the server is loaded.
Is the EduPage MCP Server server still maintained?
EduPage MCP Server is still listed as active in the MCP registry. We last reached this channel on 4 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.
What licence is the EduPage MCP Server server under?
EduPage MCP Server declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.