EduPage MCP Server
A Model Context Protocol server that lets an AI agent query and operate EduPage school accounts, including multiple schools and role-aware parent/student/teacher workflows.
Authenticate with credentials, portal auto-detect, PHPSESSID sessions, multi-school login, and 2FA completion.
View timetables for yourself, students, teachers, classes, or classrooms, including date ranges, next week, bell schedules, and next ringing time.
Retrieve grades, school year, rosters, students visible to the account, and cross-school student discovery by name or ID.
Read timeline notifications, homework/assignments, absences, events, news, homework body text, and attachment lists.
Download homework/message/event attachments to local disk without overwriting existing files.
Check substitutions, timetable changes, and missing teachers.
View meal menus and order, cancel, or rate meals where the school supports it.
Generate one-call daily summaries covering timetable, substitutions, grades, meals, homework, absences, news, events, and notifications.
Send messages, switch between parent and student sessions, and manage multi-school student caches.
Make raw authenticated GET/POST requests for endpoints not covered by dedicated tools, with text-only responses and truncation safeguards.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@EduPage MCP Server What is my timetable for tomorrow?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
EduPage MCP Server
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-apilibrary, 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:
mrtineu/edupage-mcp— also published on PyPI asedupage-mcp
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 ( | 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 ( | ✅ | ❌ | ✅ |
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_SUBDOMAINSwith 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 atschool1+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_timetableswitches 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 aPHPSESSIDcookie viamethod=),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 formerauth_status,user_id,login_auto,login_from_session, andtwo_factor_check_confirmedtools are folded into these.Timetables —
get_my_timetable,get_timetable(teacher/student/class/ classroom;end_datefor a range, formerlyget_timetable_range),get_student_timetable(student by name, cross-school),get_next_week_timetable,get_next_ringing_time,get_periods,get_school_yearStudents —
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 byEDUPAGE_SUBDOMAINSwhen set — plus session state per school)Grades —
get_gradesTimeline / 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 inadditional_data.superid),get_homework_materialto read one assignment's body text, dates and attachment list (the notification alone only carries the title and due date),download_attachmentto save one attachment to diskSubstitutions —
get_timetable_changes,get_missing_teachersMeals —
get_meals,choose_meal,sign_off_meal,rate_mealDay 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). A bundled agent skill (school-day-summary) turns that JSON into a human-readable daily report — install it withedupage-mcp-full install-skill(see Skills); every client can also callget_day_summarydirectly and read the JSON.Rosters —
get_roster(roster_type=for students, all students, teachers, classes, classrooms, or subjects),get_my_studentsActions —
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 — as an agent plugin (recommended: the server and the skill in one install)
For VS Code, GitHub Copilot and Claude Code this repo ships a plugin that
installs the server together with the school-day-summary agent skill:
copilot plugin marketplace add oliverhruby/edupage-mcp
copilot plugin install edupage@edupage-pluginsClaude Code reads the same marketplace:
claude plugin marketplace add oliverhruby/edupage-mcp
claude plugin install edupage@edupage-pluginsopencode, Cursor and Windsurf read Agent Skills but not plugins — for those, and for every other install route, see docs/PLUGINS.md.
Option B — one-shot with APM (the server and the skill in one command, optional, emerging standard)
APM (Agent Package Manager) is an emerging open standard for distributing agent context — think npm for agent skills, MCP servers and instructions. This route is optional: Option A and every route below work exactly as before. If you already use APM (or want to try it), it is the only cross-harness one-shot install here.
Install the CLI once. Requirements:
git, plusuvfor the server runtime.Windows:
winget install --id Microsoft.APM -e(orscoop bucket add apm https://github.com/microsoft/scoop-apm; scoop install apm)macOS / Linux:
brew install apmAny platform with Python 3.10+:
pip install apm-cliOther options: installation docs
Install this package:
apm install oliverhruby/edupage-mcpOne command does both:
deploys the
school-day-summaryskill into the harness's skills directory (.claude/skills/,.agents/skills/, ...);writes the
uvx edupage-mcp-fullstdio server entry into the MCP config of every harness APM detects (Claude Code, GitHub Copilot, Cursor, opencode, Codex, Gemini, Windsurf, Kiro, ...); add--target claude(or another target) to force one harness.
Verify:
apm mcp list # server registered?apm uninstall oliverhruby/edupage-mcpremoves the package and the deployed skill files, but leaves the MCP server entry it wrote into.mcp.json(and other harness configs) — delete that entry yourself if you want it gone.
Credentials are never embedded in the package: set EDUPAGE_USERNAME /
EDUPAGE_PASSWORD (and EDUPAGE_SUBDOMAINS for multi-school accounts) before
running apm install — see 2. Configure credentials.
Pinning. Append #<sha> (or #<tag> once a release contains apm.yml) to
pin an exact revision and silence APM's "dependency unpinned" warning; without
a pin, APM records the resolved commit in apm.lock.yaml. Releases up to
v0.7.0 predate the manifest, so use a commit sha until the next release.
The Claude Code plugin/marketplace install (Option A) remains supported and
unchanged — APM is an additional install route, declared in this repo's
apm.yml.
Option C — from MCP Registry (one-click, server only, no skill)
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 D — 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-fulluvx runs the package without a persistent install. If uvx is unavailable,
install uv first (pip install uv or winget install astral-sh.uv).
Option E — 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 F — 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:latestVersion 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-fullThe 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, orstreamable-httpMCP_HOST: bind host (default127.0.0.1)MCP_PORT: bind port (default8000)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.tomlpinsmcp<2(the stable FastMCP v1 API).mcp 2.xrenamedFastMCPtoMCPServerand changed the API surface; this server targets the FastMCP v1 API for simplicity and stability.
Option G — 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-full2. 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" # optionalSingle 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 underenv:directly.uvxwill auto-provision the package the first time; it must be on yourPATH.
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?" |
| Available school subdomains (live-discovered for parents), active school/subdomain, env config, and login state per school. |
"What classes do I have today?" |
| A short timetable summary for today. |
"Show me the 9.A schedule for 2026-09-10" |
| Class timetable for that date. |
"What grades do I have this term?" |
| Subject-by-subject grade overview for the selected term/year. |
"Any substitutions today?" |
| Changes, cancellations, and replacements for today. |
"What is for lunch and order option 2 for tomorrow" |
| Meal menu and order confirmation (or a clear error if unavailable). |
"Find Student A's timetable for tomorrow" |
| Student A's timetable; if found in multiple schools, one result per school. |
"List teachers and send a hello to Teacher456" |
| Teacher list plus message sent confirmation. |
"What happened at school yesterday for my kids?" |
| 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?" |
| Human-readable summary once you install the bundled |
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 / Linuxget_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 school2B) 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 activeYou can also call login once per school to add/lookup sessions incrementally.
Single school? No
EDUPAGE_SUBDOMAINSneeded — the server auto-discovers your school via the portal on startup. For two or more schools, setEDUPAGE_SUBDOMAINS(auto-login) or uselogin_all/ repeatedlogincalls.
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):
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_studentsdoes just the discovery step),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,
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? |
| Log in with username/password; | ✅ session |
| Log in to multiple schools in one call | ✅ session |
| Finish a pending 2FA login (email/app | ✅ session |
| Subdomains available to the account (live-discovered for parents; limited to | |
| Current school year | |
| Logged-in user's timetable for a date | |
| Timetable of a teacher/student/class/classroom; | |
| Student's timetable by name or id (role-aware, cross-school) | ✅ session |
| Mon–Fri timetable for next week | |
| Next bell (break/lesson) at a given time | |
| Bell schedule (period start/end times) | |
| Grades, optionally by year & term | |
| Timeline notifications, one | |
| Full body text, dates and attachment list of one assignment, by | |
| Download any authenticated attachment to disk — absolute URL, or the relative | ✅ disk |
| Substitutions / timetable changes for a date | |
| Teachers missing on a date | |
| 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 | |
| Meal menu (all 5 slots: breakfast, snack, lunch, afternoon snack, dinner) | |
| Order a meal | ✅ |
| Cancel an ordered meal | ✅ |
| Rate a meal (quality/quantity) | ✅ |
| One | |
| Students visible to the logged-in account (one school) | |
| Look up a student's person_id by name (cross-school) | |
| Auto-discover students across the configured | |
| Clear cached student rosters (one school or all schools) | ✅ cache |
| Available school subdomains + role/session per school | |
| Send a message to a user | ✅ |
| Switch to a student account by id or name (parent only) | ✅ session |
| Switch back to the parent account | ✅ session |
| Raw text request through the active session (GET/POST); refuses binary bodies and points at | ✅ |
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_attachmentis the one tool that writes to local disk: it saves intodest_dir(default<tempdir>/homework), takes the name from the server'scontent-dispositionwhen 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_requestreturns text only: a binary body is refused with a pointer todownload_attachment, and a text body over 60000 characters comes back truncated with an explicittruncated: true.get_timelinecategorieshomework,assignments,absences,eventsandnewsderive their data from the timeline notifications — if the school doesn't push certain event types, those categories may return empty lists.get_missing_teachersis marked experimental upstream (parses HTML from the substitution page) and can raise if a teacher's name no longer matches.Meal
rate_mealand ordering depend on the school publishing menus with the matching identifiers; not all schools expose ratings.get_mealsfirst 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 areNone.
Skills
The package ships one agent skill (school-day-summary). It teaches a
coding agent how to turn get_day_summary JSON into a human-readable daily
school report — "what happened yesterday for my kids?"
Installing the plugin ships this skill together with the server. To install the skill on its own:
# already installed with the server:
edupage-mcp-full install-skill
# ...or with nothing but uv:
uvx --from edupage-mcp-full edupage-mcp-full install-skill --agent copilotEach agent it touches prints one line:
installed -> C:\Users\you\.copilot\skills\school-day-summary\SKILL.md
skipped (identical) -> /home/you/.claude/skills/school-day-summary/SKILL.md
skipped (exists, differs, use --force) -> /home/you/.agents/skills/school-day-summary/SKILL.mdRestart your agent afterwards so it picks the skill up.
Options
Option | Effect |
| Install into that agent only. With no flag: every agent directory that already exists. |
| Print the resolved |
| Report what would be written, write nothing. |
| Overwrite an existing |
Where it goes
| Installed to |
|
|
|
|
|
|
|
|
Re-running is safe: a file that is already identical is reported as
skipped (identical), and one that differs is left alone unless you pass
--force, so a skill you edited locally is never silently clobbered.
Prefer to place the file yourself? edupage-mcp-full install-skill --print
prints the exact path of the bundled SKILL.md in the installed package.
Other MCP clients (Claude, Cursor, or any client without a skill system) can
just call get_day_summary directly — they receive the full structured JSON.
Formatting is client-specific. The base MCP protocol has no skill concept (an
io.modelcontextprotocol/skills extension is specified, but this server does not
implement it yet), which is why distribution goes through the plugin and the
installer above. See docs/PLUGINS.md for every install route.
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 resultingPHPSESSID.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 repeatedlogincalls). 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 :-)
![]()
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 toolschoose_mealAIdempotent
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.
Inputs: number (required), date_str (required), meal_type (required), subdomain.
clear_student_cacheAIdempotent
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': }.
Inputs: subdomain.
custom_requestADestructive
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.
Inputs: url (required), method (required), data, headers, +1 more.
download_attachmentADestructiveIdempotent
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.
Inputs: url (required), dest_dir, filename, subdomain.
find_studentARead-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.
Inputs: name (required), subdomain.
get_day_summaryARead-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_idis provided: report for that specific student (found across all schools unlesssubdomainscopes 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. Setfull=Trueto 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.
Inputs: full, name, date_str, subdomain, +1 more.
get_gradesARead-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.
Inputs: term, year, subdomain.
get_homework_materialARead-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]'.
Inputs: superid (required), subdomain.
get_mealsARead-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.
Inputs: date_str, subdomain.
get_missing_teachersARead-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.
Inputs: date_str, subdomain.
get_my_studentsARead-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.
Inputs: subdomain.
get_my_timetableARead-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.
Inputs: date_str, subdomain.
get_next_ringing_timeARead-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.
Inputs: subdomain, date_time_str.
get_next_week_timetableARead-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.
Inputs: subdomain.
get_periodsARead-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.
Inputs: subdomain.
get_rosterARead-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.
Inputs: roster_type (required), subdomain.
get_school_yearARead-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': ...}.
Inputs: subdomain.
get_student_timetableARead-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.
Inputs: name, date_str, subdomain, student_id.
get_subdomainsARead-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.
Inputs: no named parameters.
get_timelineARead-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.
Inputs: category, date_from, subdomain.
get_timetableARead-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.
Inputs: target_id (required), target_type (required), date_str, end_date, +1 more.
get_timetable_changesARead-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.
Inputs: date_str, subdomain.
loginAIdempotent
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.
Inputs: method, password, username, subdomain, +1 more.
login_allAIdempotent
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.
Inputs: passwords, usernames, subdomains.
rate_mealADestructiveIdempotent
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 shows, so it reaches the
canteen and cannot be deleted through this server.
- Upstream sends a fixed meal-slot field ("jedlo_dna" = 2, lunch) with
the rating, so a snack or afternoon-snack score is recorded against
the lunch entry.
Inputs: quality (required), date_str (required), quantity (required), meal_type (required), +1 more.
scan_studentsARead-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.
Inputs: no named parameters.
send_messageADestructive
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.
Inputs: body (required), recipient_id (required), subdomain.
sign_off_mealADestructiveIdempotent
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}.
Inputs: date_str (required), meal_type (required), subdomain.
switch_to_parentAIdempotent
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.
Inputs: subdomain.
switch_to_studentAIdempotent
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.
Inputs: name, subdomain, student_id.
two_factor_finishAIdempotent
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.
Inputs: code, subdomain, poll_seconds.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 31 tools
Several clusters overlap substantially: get_my_timetable / get_student_timetable / get_timetable / get_next_week_timetable all return timetables, and get_my_students / scan_students / find_student / get_roster all surface student lists. The descriptions do cross-reference each other well (e.g. 'prefer X over Y'), which mitigates but does not eliminate misselection risk.
Every tool uses snake_case with a predictable verb_noun pattern (get_*, find_student, scan_students, choose_meal, sign_off_meal, send_message, download_attachment). No mixing of conventions or vague single-word names beyond login helpers, which are still clear.
At 31 tools this sits in the heavy range, though the domain (auth, timetable, grades, meals, messaging, rosters, raw API) is genuinely broad. Some redundancy exists within the timetable and student-discovery clusters, so a handful of tools could be consolidated.
The surface covers authentication (login, login_all, 2FA), session switching, read paths for timetable/grades/timeline/meals/rosters, write paths for meals, ratings and messages, plus attachment download, cache management, and a raw request escape hatch. Coverage is unusually thorough with few dead ends.
Maintenance
Related MCP Connectors
Email, calendar and contacts for AI agents: Gmail, Microsoft 365 and IMAP accounts behind one API.
Connect AI assistants to Edusign attendance, courses, learners, documents, and API docs via OAuth.
ERC-8004 agent identity, on-chain subscriptions, reputation, A2A tasks and chat over 53 tools.
Email inboxes, webhooks, mock APIs, 2FA codes, approvals and 170 tools for AI agents.
Related MCP Servers
- AlicenseAqualityCmaintenanceMCP server that connects Claude to Edupage, a school information system used across Europe. Provides access to timetables, grades, homework, messages, students, teachers, and more.251GPL 3.0
- FlicenseNot gradedqualityBmaintenanceConnects AI agents to the École Directe API to access grades, homework, timetables, school life, messages, and documents.-
- AlicenseAqualityCmaintenanceLets an AI agent read and manage a family's EduPage school data, including timetables, homework, grades, messages, absences, events, and canteen meals, and also send messages to teachers or order meals.161MIT
- AlicenseAqualityBmaintenanceEnables AI assistants to access Librus Synergia school data, including grades, attendance, timetables, homework, calendar, announcements, messages, formative assessments, school-year history, class-free days, and student information. Supports multiple logins and self-hosted HTTP access for agents on other machines.31742 PyPI15MIT