Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, get in touch and we’ll put it right.

EduPage MCP Server

PYPI · EDUPAGE-MCP-FULL

EduPage MCP: timetables, grades, homework, meals, messages, 2FA, multi-school, role-aware.

Available components

+21 this week 91 Trust /100

Last scanned (1 day ago)

Scores higher than 94% of PyPI servers

Category Education and Research

Trust breakdown (7 categories)

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
Changelog

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
Diagnostics

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 →

MCP tools · 31 exposed · ~7,228 tokens

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 →

Tool Tokens
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`.

NameTypeReqDescription
date_strstringyes–
meal_typestringyes–
numberintegeryes–
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>}.

NameTypeReqDescription
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`.

NameTypeReqDescription
data–––
headers–––
methodstringyes–
subdomain–––
urlstringyes–

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…

NameTypeReqDescription
dest_dir–––
filename–––
subdomain–––
urlstringyes–

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.

NameTypeReqDescription
namestringyes–
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.

NameTypeReqDescription
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.

NameTypeReqDescription
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]'.

NameTypeReqDescription
subdomain–––
superidstringyes–

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.

NameTypeReqDescription
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.

NameTypeReqDescription
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.

NameTypeReqDescription
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`.

NameTypeReqDescription
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.

NameTypeReqDescription
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`.

NameTypeReqDescription
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.

NameTypeReqDescription
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`.

NameTypeReqDescription
roster_typestringyes–
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': ...}.

NameTypeReqDescription
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.

NameTypeReqDescription
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.

NameTypeReqDescription
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`.

NameTypeReqDescription
date_str–––
end_date–––
subdomain–––
target_idstringyes–
target_typestringyes–

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`.

NameTypeReqDescription
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.

NameTypeReqDescription
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`.

NameTypeReqDescription
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…

NameTypeReqDescription
date_strstringyes–
meal_typestringyes–
qualityintegeryes–
quantityintegeryes–
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`.

NameTypeReqDescription
bodystringyes–
recipient_idstringyes–
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}.

NameTypeReqDescription
date_strstringyes–
meal_typestringyes–
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.

NameTypeReqDescription
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.

NameTypeReqDescription
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.

NameTypeReqDescription
code–––
poll_seconds–––
subdomain–––

No output schema declared.

No examples provided.

Install

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

# add to Claude Code
claude mcp add oliverhruby-edupage-mcp -- uvx edupage-mcp-full
Common questions

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.