EduPage MCP Server
# EduPage MCP Server
<!-- mcp-name: io.github.oliverhruby/edupage-mcp -->
[](https://github.com/oliverhruby/edupage-mcp/releases)
[](https://glama.ai/mcp/servers/oliverhruby/edupage-mcp)
[](https://github.com/oliverhruby/edupage-mcp/actions/workflows/quality-gates.yml)
[](https://github.com/oliverhruby/edupage-mcp/actions/workflows/security.yml)
[](https://github.com/oliverhruby/edupage-mcp/actions/workflows/container-security.yml)
[](https://github.com/oliverhruby/edupage-mcp/actions/workflows/upstream-coverage.yml)
## Project
A Model Context Protocol (MCP) server that exposes the full functionality of the
[`edupage-api`](https://github.com/EdupageAPI/edupage-api) Python library to AI
agents such as opencode, Claude, Cursor and any other MCP client.
EduPage is a school information system used across Europe. This server lets you
query and operate a student / teacher / parent EduPage account directly from
your agent: timetables, grades, homework, substitutions, meals (including
ordering), messages, rosters, parent child-switching and more — including
**multiple schools** (e.g. two children attending different schools).
> **⚠️ Unofficial API.** Like all EduPage MCP servers, this relies on the
> community-maintained [`edupage-api`](https://github.com/EdupageAPI/edupage-api)
> library, which talks to EduPage's undocumented endpoints. Use read-only
> features freely; use the write features (`send_message`, meal ordering, child
> switching) carefully.
---
## Table of Contents
- [Why another EduPage MCP server?](#why-another-edupage-mcp-server)
- [What it provides](#what-it-provides)
- [Getting started](#getting-started)
- [Prerequisites](#prerequisites)
- [1. Install](#1-install)
- [2. Configure credentials](#2-configure-credentials)
- [3. Register with your MCP client](#3-register-with-your-mcp-client)
- [Prompt examples](#prompt-examples)
- [Multiple schools (subdomains)](#multiple-schools-subdomains)
- [Tool reference](#tool-reference)
- [Data & safety notes](#data--safety-notes)
- [Contributing](#contributing)
- [Limitations](#limitations)
- [Support](#support)
- [License](#license)
---
## Why another EduPage MCP server?
Two other EduPage MCP servers already exist:
- [`mrtineu/edupage-mcp`](https://github.com/mrtineu/edupage-mcp) — also
published on PyPI as [`edupage-mcp`](https://pypi.org/project/edupage-mcp/)
- [`mhlavac/edupage-mcp`](https://github.com/mhlavac/edupage-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** (`PHPSESSID`) | partial (portal) | basic only | ✅ |
| Timetables (own + any teacher/class/room) | ✅ | ✅ | ✅ |
| Grades (all / by term & year) | ✅ | ✅ | ✅ |
| Substitutions / timetable changes | ✅ | ✅ | ✅ |
| Meals — **read menu** | ✅ | ✅ | ✅ |
| Meals — **choose / sign-off / rate** | ❌ | ❌ | ✅ |
| Send messages (`send_message`) | ✅ | ❌ | ✅ |
| Parent **student switching** (switch to/from student) | partial (list) | ❌ | ✅ |
| Next ringing time / bell schedule | ❌ | ❌ | ✅ |
| Raw session **custom request** | ❌ | ❌ | ✅ |
| **Multiple schools** (auto-login + discovery) | ❌ | ❌ | ✅ |
| **Role-aware** (parent / student / teacher) | ❌ | ❌ | ✅ |
| **Day summaries** (one-call daily report) | ❌ | ❌ | ✅ |
| Homework — **body text + attachments** | ❌ | ✅ | ✅ |
| **Download a homework file to disk** | ❌ | ✅ | ✅ |
**Key differentiators:**
- **Multi-school automatic discovery.** Set `EDUPAGE_SUBDOMAINS` with one shared
login and the server auto-discovers students across all schools — no need to
maintain a manual "Student → school1, Student → school2" mapping. A student at
two schools (e.g. Student at `school1` + `school2`) is found automatically
with separate per-school results.
- **Role-aware tools.** The server detects whether you're a parent, student, or
teacher at each school and behaves accordingly — `get_student_timetable`
switches to the student account for parents, returns direct timetables for
students. No tool duplication.
- **Full write surface.** Meal ordering/rating, message sending, student switching
— the other servers don't cover these.
*Comparison checked 2026-09-28. If a row is wrong, please open an issue.*
---
## What it provides
A single stdio MCP server exposing **31 tools** (published on PyPI as
[`edupage-mcp-full`](https://pypi.org/project/edupage-mcp-full/)):
- **Authentication** — `login` (by credentials, portal auto-detect, or a
`PHPSESSID` cookie via `method=`), `login_all` (multi-school, one call),
`two_factor_finish` (complete a pending 2FA), `get_subdomains` (schools
available to the account with role/user id per school + env config, live
subdomain discovery for parents). The former `auth_status`,
`user_id`, `login_auto`, `login_from_session`, and
`two_factor_check_confirmed` tools are folded into these.
- **Timetables** — `get_my_timetable`, `get_timetable` (teacher/student/class/
classroom; `end_date` for a range, formerly `get_timetable_range`),
`get_student_timetable` (student by name, cross-school), `get_next_week_timetable`,
`get_next_ringing_time`, `get_periods`, `get_school_year`
- **Students** — `find_student` (name → person_id, cross-school),
`get_student_timetable` (cross-school, role-aware), `scan_students`
(auto-discover all students across schools), `get_my_students` (classmates or
school-wide for parents), `switch_to_student` (by id **or** name, parent only),
`switch_to_parent`, `clear_student_cache` (force refresh cached student lists)
- **Schools** — `get_subdomains` (subdomains available to the account — live-discovered for parents, limited by `EDUPAGE_SUBDOMAINS` when set — plus session state per school)
- **Grades** — `get_grades`
- **Timeline / notifications** — `get_timeline` (`category=` for homework,
assignments, absences, events, news, or full history since a date)
- **Homework** — `get_timeline(category='homework')` to list assignments
(ids in `additional_data.superid`), `get_homework_material` to read one
assignment's **body text**, dates and attachment list (the notification alone
only carries the title and due date), `download_attachment` to save one
attachment to disk
- **Substitutions** — `get_timetable_changes`, `get_missing_teachers`
- **Meals** — `get_meals`, `choose_meal`, `sign_off_meal`, `rate_meal`
- **Day summaries** — `get_day_summary` (one call: timetable, substitutions,
missing teachers, grades, meals, homework, assignments, absences, news,
events, notifications for a date — "what happened yesterday at school" in a
single round trip; each section is isolated so one failure doesn't kill the
report). A bundled agent skill (`school-day-summary`) turns that JSON into a
human-readable daily report — install it with `edupage-mcp-full
install-skill` (see [Skills](#skills)); every client can also call
`get_day_summary` directly and read the JSON.
- **Rosters** — `get_roster` (`roster_type=` for students, all students,
teachers, classes, classrooms, or subjects), `get_my_students`
- **Actions** — `send_message`, `switch_to_student`, `switch_to_parent`, `custom_request`
---
## Getting started
You need an MCP-capable client (opencode, Claude Desktop, Cursor, etc.).
### 1. Install
If you are using an AI coding client, a simple prompt is often enough to get
started, for example: "Install the EduPage MCP as described in this GitHub
repository oliverhruby/edupage-mcp". Most MCP-capable clients can then guide
you through the available setup options.
**Option A — 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:
```bash
copilot plugin marketplace add oliverhruby/edupage-mcp
copilot plugin install edupage@edupage-plugins
```
Claude Code reads the same marketplace:
```bash
claude plugin marketplace add oliverhruby/edupage-mcp
claude plugin install edupage@edupage-plugins
```
opencode, Cursor and Windsurf read Agent Skills but not plugins — for those, and
for every other install route, see [docs/PLUGINS.md](docs/PLUGINS.md).
**Option B — one-shot with [APM](https://microsoft.github.io/apm/) (the server *and* the skill in one command, optional, emerging standard)**
[APM](https://microsoft.github.io/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.
1. Install the CLI once. Requirements: `git`, plus `uv` for the server runtime.
- Windows: `winget install --id Microsoft.APM -e` (or `scoop bucket add apm https://github.com/microsoft/scoop-apm; scoop install apm`)
- macOS / Linux: `brew install apm`
- Any platform with Python 3.10+: `pip install apm-cli`
- Other options: [installation docs](https://microsoft.github.io/apm/getting-started/installation/)
2. Install this package:
```bash
apm install oliverhruby/edupage-mcp
```
One command does both:
- deploys the `school-day-summary` skill into the harness's skills directory
(`.claude/skills/`, `.agents/skills/`, ...);
- writes the `uvx edupage-mcp-full` stdio 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.
3. Verify:
```bash
apm mcp list # server registered?
```
`apm uninstall oliverhruby/edupage-mcp` removes 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](#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](https://registry.modelcontextprotocol.io/).
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`.
```bash
uvx edupage-mcp-full
# or, if you prefer pip (into whatever environment your MCP client uses):
pip install edupage-mcp-full
```
`uvx` runs the package without a persistent install. If `uvx` is unavailable,
install `uv` first (`pip install uv` or `winget install astral-sh.uv`).
**Option 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`.
```bash
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):
```bash
docker pull ghcr.io/oliverhruby/edupage-mcp:latest
docker run --rm -i \
-e EDUPAGE_USERNAME=your_username \
-e EDUPAGE_PASSWORD=your_password \
ghcr.io/oliverhruby/edupage-mcp:latest
```
Version tags are also available (for example `v0.4.0`) if you prefer pinned
images.
Build locally from source (fallback):
```bash
docker build -t edupage-mcp-full .
docker run --rm -i \
-e EDUPAGE_USERNAME=your_username \
-e EDUPAGE_PASSWORD=your_password \
edupage-mcp-full
```
The container uses the same environment variables described in
[Configure credentials](#2-configure-credentials). It also includes a
`HEALTHCHECK` (stdio process liveness by default; local TCP check in HTTP
transport modes).
For HTTP transports, set optional runtime vars:
- `MCP_TRANSPORT`: `stdio` (default), `sse`, or `streamable-http`
- `MCP_HOST`: bind host (default `127.0.0.1`)
- `MCP_PORT`: bind port (default `8000`)
- `MCP_API_KEY`: optional bearer token for HTTP auth
When `MCP_API_KEY` is set, HTTP requests must include `Authorization: Bearer <key>`.
If `MCP_API_KEY` is not set, HTTP endpoints are unauthenticated. For production,
prefer proper authentication and TLS via a reverse proxy or API gateway.
> `pyproject.toml` pins `mcp<2` (the stable FastMCP v1 API). `mcp 2.x` renamed
> `FastMCP` to `MCPServer` and changed the API surface; this server targets the
> FastMCP v1 API for simplicity and stability.
**Option G — development from source**
Use this if you are contributing or debugging locally.
Requirements: Python **3.10+**.
```bash
git clone https://github.com/oliverhruby/edupage-mcp.git
cd edupage-mcp
uv sync # or: python -m venv .venv && .venv/bin/python -m pip install -e .
uv run edupage-mcp-full
```
### 2. Configure credentials
Either set environment variables **or** pass credentials to `login` (see
[Prompt examples](#prompt-examples)).
```bash
# Windows (persistent, per-user)
setx EDUPAGE_USERNAME "your_username"
setx EDUPAGE_PASSWORD "your_password"
setx EDUPAGE_SUBDOMAINS "s1,s2,s3" # optional: multiple schools (auto-login + discovery)
# macOS / Linux
export EDUPAGE_USERNAME="your_username"
export EDUPAGE_PASSWORD="your_password"
export EDUPAGE_SUBDOMAINS="s1,s2,s3" # optional
```
**Single school?** Just set `EDUPAGE_USERNAME` + `EDUPAGE_PASSWORD`. The server
auto-discovers your school via the EduPage portal on startup — no subdomain needed.
**Multiple schools?** Add `EDUPAGE_SUBDOMAINS` (comma-separated). The server
logs into all of them on startup with your shared credentials.
### 3. Register with your MCP client
**opencode** — add to `~/.config/opencode/opencode.json` (or `opencode.jsonc`):
```jsonc
{
"mcp": {
"edupage": {
"type": "local",
"enabled": true,
"command": ["uvx", "edupage-mcp-full"],
"env": {
"EDUPAGE_USERNAME": "{env:EDUPAGE_USERNAME}",
"EDUPAGE_PASSWORD": "{env:EDUPAGE_PASSWORD}",
"EDUPAGE_SUBDOMAINS": "{env:EDUPAGE_SUBDOMAINS}"
}
}
}
}
```
> Put credentials in your shell/environment (or a `.env`) and reference them with
> `{env:VAR}`, or hardcode them under `env:` directly. `uvx` will auto-provision
> the package the first time; it must be on your `PATH`.
**Claude Desktop / Cursor** — use `claude_desktop_config.json` /
`.mcp.json` with a `mcpServers` entry in the standard shape, pointing
`command`/`args` at the venv python and the `edupage_mcp.py` path, plus an
`env` block with your credentials.
After editing client config, **restart the client** so the MCP server is loaded.
---
## Prompt examples
| User prompt | Likely tool call(s) | Expected response |
|---|---|---|
| "Are we connected and logged in?" | `get_subdomains` | Available school subdomains (live-discovered for parents), active school/subdomain, env config, and login state per school. |
| "What classes do I have today?" | `get_my_timetable` | A short timetable summary for today. |
| "Show me the 9.A schedule for 2026-09-10" | `get_timetable target_type="class" target_id="9.A" date_str="2026-09-10"` | Class timetable for that date. |
| "What grades do I have this term?" | `get_grades term="FIRST" year=2026` | Subject-by-subject grade overview for the selected term/year. |
| "Any substitutions today?" | `get_timetable_changes` | Changes, cancellations, and replacements for today. |
| "What is for lunch and order option 2 for tomorrow" | `get_meals` → `choose_meal date_str="2026-09-10" meal_type="lunch" number=2` | Meal menu and order confirmation (or a clear error if unavailable). |
| "Find Student A's timetable for tomorrow" | `get_student_timetable name="Student A" date_str="2026-09-10"` | Student A's timetable; if found in multiple schools, one result per school. |
| "List teachers and send a hello to Teacher456" | `get_roster roster_type="teachers"` → `send_message recipient_id="Teacher456" body="Hello!"` | Teacher list plus message sent confirmation. |
| **"What happened at school yesterday for my kids?"** | `get_day_summary date_str="2026-09-09"` (discovery index) → `get_day_summary date_str="2026-09-09" name="Student A" subdomain="school-a"` → `... name="Student B" subdomain="school-b"` | **Discovery-first**: the no-name call lists each child per school; then one complete daily report call per child (timetable, substitutions, missing teachers, grades, meals, homework, assignments, absences, news, events, notifications). Keeps each response small and avoids mixing schools/students. |
| **"How was school today for Student A?"** | `get_day_summary name="Student A"` (defaults to today) | Human-readable summary once you install the bundled `school-day-summary` skill (`edupage-mcp-full install-skill`). |
---
## 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**:
```bash
setx EDUPAGE_SUBDOMAINS "school1,school2,school3" # Windows
export EDUPAGE_SUBDOMAINS="school1,school2,school3" # macOS / Linux
```
```text
get_subdomains # lists school1, school2, school3 (logged in, with role)
scan_students # discovers Student A and Student B across those schools
get_student_timetable name="Student A" # is found at school1 AND school2
```
**B) On demand with `login_all`.** Authenticate several schools at once, then pass
`subdomain` to any data tool (it defaults to the last active subdomain when
omitted):
```text
login_all subdomains="school1,school2" usernames="u1,u2" passwords="p1,p2"
get_my_timetable subdomain="school1"
get_my_timetable subdomain="school2"
get_subdomains # shows all logged-in subdomains + which is active
```
You can also call `login` once per school to add/lookup sessions incrementally.
> **Single school?** No `EDUPAGE_SUBDOMAINS` needed — the server auto-discovers
> your school via the portal on startup. For two or more schools, set
> `EDUPAGE_SUBDOMAINS` (auto-login) or use `login_all` / repeated `login` calls.
---
## Students by name (e.g. "timetable for Student A")
Because the server **auto-discovers students across the configured
`EDUPAGE_SUBDOMAINS`** (or every logged-in school when the variable is unset),
you don't need to know or state which school a student is in. Just ask for the
timetable by name and the server searches every school in scope:
```text
"timetable for Student A" -> get_student_timetable name="Student A"
```
`get_student_timetable` (with no `subdomain`):
1. searches **every school in the discovery scope** — the configured
`EDUPAGE_SUBDOMAINS`, or all logged-in schools when unset — for a student
whose first/last/full name matches (`scan_students` does just the discovery
step),
2. for each school where the student is found, switches to the student account if
you're logged in as a parent, returns that student's timetable for the date, and
switches back to the parent account afterwards,
3. returns **one result per school**.
A student attending **more than one school** (e.g. Student at `school1` +
`school2`) therefore yields a list of two per-school timetables — separate
results, never merged. This is the built-in replacement for maintaining a
manual "Student → school1" mapping: with `EDUPAGE_SUBDOMAINS` set, discovery is
fully automatic.
---
## Tool reference
| Tool | Description | Writes? |
|---|---|---|
| `login` | Log in with username/password; `method="credentials"` (default), `method="auto"` (portal auto-detect, formerly `login_auto`), or `method="session"` with a `PHPSESSID` cookie (formerly `login_from_session`). Env vars supported. | ✅ session |
| `login_all` | Log in to multiple schools in one call | ✅ session |
| `two_factor_finish` | Finish a pending 2FA login (email/app `code` or `poll_seconds` device confirmation; formerly `two_factor_check_confirmed` + `two_factor_finish`) | ✅ session |
| `get_subdomains` | Subdomains available to the account (live-discovered for parents; limited to `EDUPAGE_SUBDOMAINS` when set) + role/user id per school, active subdomain, failed logins, env config (formerly `auth_status`, `user_id`) | |
| `get_school_year` | Current school year | |
| `get_my_timetable` | Logged-in user's timetable for a date | |
| `get_timetable` | Timetable of a teacher/student/class/classroom; `end_date` for a daily range (formerly `get_timetable_range`) | |
| `get_student_timetable` | Student's timetable by name or id (role-aware, cross-school) | ✅ session |
| `get_next_week_timetable` | Mon–Fri timetable for next week | |
| `get_next_ringing_time` | Next bell (break/lesson) at a given time | |
| `get_periods` | Bell schedule (period start/end times) | |
| `get_grades` | Grades, optionally by year & term | |
| `get_timeline` | Timeline notifications, one `category=` at a time: `recent`, `history` (since `date_from`), `homework`, `assignments`, `absences`, `events`, `news` (formerly `get_notifications`, `get_notification_history`, `get_homework`, `get_assignments`, `get_absences`, `get_upcoming_events`, `get_news`) | |
| `get_homework_material` | Full body text, dates and attachment list of one assignment, by `superid` from `get_timeline(category='homework')` → `additional_data.superid` | |
| `download_attachment` | Download any authenticated attachment to disk — absolute URL, or the relative `/elearning/…` key from `get_timeline`; refuses school/login pages (default `<tempdir>/homework`, never overwrites) | ✅ disk |
| `get_timetable_changes` | Substitutions / timetable changes for a date | |
| `get_missing_teachers` | Teachers missing on a date | |
| `get_day_summary` | One-call daily report (timetable, substitutions, teachers, grades, meals incl. breakfast/dinner when published, homework, assignments, absences, news, events, notifications) for a date; student by name/id (role-aware). **Discovery-first**: parent without `name`/`student_id` returns a lightweight per-school student index (`mode:"discovery"`); pass `full=True` to build full reports for every child. Bundles the `school-day-summary` agent skill (installed with `edupage-mcp-full install-skill`) for human-readable output. | |
| `get_meals` | Meal menu (all 5 slots: breakfast, snack, lunch, afternoon snack, dinner) | |
| `choose_meal` | Order a meal | ✅ |
| `sign_off_meal` | Cancel an ordered meal | ✅ |
| `rate_meal` | Rate a meal (quality/quantity) | ✅ |
| `get_roster` | One `roster_type=` at a time: `students` (logged-in user's class), `all_students` (whole school, short list), `teachers`, `classes`, `classrooms`, `subjects` (formerly `get_students`, `get_all_students`, `get_teachers`, `get_classes`, `get_classrooms`, `get_subjects`) | |
| `get_my_students` | Students visible to the logged-in account (one school) | |
| `find_student` | Look up a student's person_id by name (cross-school) | |
| `scan_students` | Auto-discover students across the configured `EDUPAGE_SUBDOMAINS` (or all logged-in schools when unset) | |
| `clear_student_cache` | Clear cached student rosters (one school or all schools) | ✅ cache |
| `get_subdomains` | Available school subdomains + role/session per school | |
| `send_message` | Send a message to a user | ✅ |
| `switch_to_student` | Switch to a student account by id or name (parent only) | ✅ session |
| `switch_to_parent` | Switch back to the parent account | ✅ session |
| `custom_request` | Raw text request through the active session (GET/POST); refuses binary bodies and points at `download_attachment` | ✅ |
---
## Data & safety notes
- Most tools are **read-only**. The ones marked **Writes? ✅** mutate EduPage
state (sent messages, ordered meals, switched accounts). Use them with care.
- `download_attachment` is the one tool that writes to **local disk**: it saves
into `dest_dir` (default `<tempdir>/homework`), takes the name from the
server's `content-disposition` when present, and appends ` (1)`, ` (2)`, ...
rather than overwriting anything. An HTML login/error page is refused instead
of being written out as an attachment.
- `custom_request` returns **text only**: a binary body is refused with a
pointer to `download_attachment`, and a text body over 60000 characters
comes back truncated with an explicit `truncated: true`.
- `get_timeline` categories `homework`, `assignments`, `absences`, `events` and
`news` derive their data from the **timeline notifications** — if the school
doesn't push certain event types, those categories may return empty lists.
- `get_missing_teachers` is marked **experimental** upstream (parses HTML from
the substitution page) and can raise if a teacher's name no longer matches.
- Meal `rate_meal` and ordering depend on the school publishing menus with the
matching identifiers; not all schools expose ratings.
- `get_meals` first tries the per-student meal-ordering endpoint (needed for
ordering/ratings). When a school doesn't enable that, it falls back to the
school's **public canteen menu widget** (`/menu/?wid=menu_CanteenMenu_1`).
All five slots (breakfast/snack/lunch/afternoon_snack/dinner) are always
returned; slots the school doesn't publish are ``None``.
---
## Skills
The package 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](#1-install) ships this skill together with the server.
To install the skill on its own:
```bash
# 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 copilot
```
Each 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.md
```
Restart your agent afterwards so it picks the skill up.
### Options
| Option | Effect |
|---|---|
| `--agent {opencode,copilot,claude,agents,all}` | Install into that agent only. With no flag: every agent directory that already exists. `all` forces all four and creates the missing directories. |
| `--print` | Print the resolved `SKILL.md` path and exit — writes nothing. |
| `--dry-run` | Report what would be written, write nothing. |
| `--force` | Overwrite an existing `SKILL.md` that differs. |
### Where it goes
| `--agent` | Installed to |
|---|---|
| `opencode` | `~/.config/opencode/skills/` |
| `copilot` | `~/.copilot/skills/` |
| `claude` | `~/.claude/skills/` |
| `agents` | `~/.agents/skills/` — the cross-vendor location (Copilot, Codex, Cursor and opencode all honour it) |
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](docs/PLUGINS.md) for every install route.
---
## Contributing
Contributor and maintainer guidance is in [CONTRIBUTING.md](CONTRIBUTING.md).
- Contribution workflow and local setup
- Architecture and implementation details
- Release process (PyPI, GitHub Releases, GHCR)
- CI quality gates and upstream coverage drift checks
---
## Limitations
- **Unofficial/read-mostly by design.** EduPage can change its endpoints at any
time; reliability ultimately depends on `edupage-api`, not this wrapper.
- **No CAPTCHA bypass.** If EduPage presents a CAPTCHA during login, log in via
browser first, then use `login method="session"` with the resulting `PHPSESSID`.
- **2FA requires human interaction** (approve on device or provide a code).
- **Parent/teacher accounts** are only partially verified upstream; some parent
methods are best-effort.
- The auth session lives for the lifetime of the MCP server process; restarting
the client means logging in again.
- Cross-school student discovery depends on being logged into all relevant
schools (via `EDUPAGE_SUBDOMAINS`, `login_all`, or repeated `login` calls).
If a school is not logged in, that student's results from that school cannot
be discovered.
---
## Support
If you like this project and want to support or request a feature, send me a
beer, it keeps my mind relaxed and ideas will come :-)
[](https://www.paypal.me/oliverhruby/)
---
## License
[MIT](LICENSE) © 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).
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.