Skip to main content
Glama

Edvibe School MCP

Status: experimental / unofficial. This is Ruslan Sungurov's private pilot, not an official Edvibe product. Do not deploy, publish, enable telemetry, or connect live schools without the approvals described in PLAN.md.

A stateless Model Context Protocol server over the official Edvibe School API. The v1 audience is Edvibe/ProgressMe schools on the Pro plan where the School API module is available.

Current state

  • The MCP contract contains exactly 78 tools: 35 read + 24 write + 17 high-risk + 2 sensitive.

  • Local STDIO and stateless Streamable HTTP transports are implemented.

  • The private HTTP pilot is currently served at https://edvibe.sungurov.com/mcp.

  • A privacy-bounded HTTP telemetry pipeline and Russian internal dashboard are implemented locally.

  • Telemetry and /admin/analytics are disabled by default and have not been enabled on the pilot server. Deployment and activation require separate approval.

  • Central telemetry intentionally excludes STDIO and never receives the user's original prompt.

The public tool names, arguments and responses remain unchanged. LoginPupil and LoginTeacher remain in the 78-tool contract; their actual public disablement and a tenant allowlist are separate security tasks.

Related MCP server: SchoolBridge

Contract at a glance

Class

Count

Meaning

readOnlyHint

destructiveHint

read

35

Read-only

true

false

write

24

Ordinary state change

false

false

high-risk

17

Destructive / hard to reverse

false

true

sensitive

2

Returns a login token

false

false + explicit warning

Total

78

Risk is determined by operation semantics, not only by the HTTP method.

HTTP connection

POST /mcp requires two headers and accepts a third optional header:

Header

Value

Authorization

Bearer <EDVIBE_API_KEY>

X-Edvibe-School-Domain

Bare school hostname

X-Edvibe-Client-Id

Optional persistent UUID v4 for this client installation

Old configurations without X-Edvibe-Client-Id continue to work. Invalid IDs are ignored. A valid ID enables installation coverage and anonymous scenario metrics; the raw UUID is never persisted.

GET /healthz remains unauthenticated and is excluded from product metrics. All other non-MCP paths return a generic error, except the separately protected analytics routes when enabled.

Local workflow

Requirements: Node.js 20+.

npm ci
npm run ci

npm run ci runs both the immutable 78-tool contract validator and the node:test runtime/privacy suite.

Run STDIO (no centralized telemetry):

MCP_TRANSPORT=stdio node src/index.js

Run HTTP locally with telemetry and analytics disabled:

MCP_TRANSPORT=http TELEMETRY_ENABLED=false ANALYTICS_DASHBOARD_ENABLED=false node src/index.js

Credentials for School API access come from the MCP client configuration. Never put real API keys in Git, examples, test fixtures or shell command history.

Safe HTTP telemetry

The telemetry API accepts only a strict event schema:

  • service_started

  • mcp_initialize_completed

  • mcp_request_rejected

  • tool_call_completed

  • service_stopped

  • telemetry_storage_degraded

Allowed data is limited to UTC time, a server request ID, transport, HMAC pseudonyms, normalized client family/version, MCP method, tool/group/risk, outcome, a fixed safe error code, upstream status and numeric timings.

The logger rejects headers, IP/User-Agent, URLs/query strings, arguments/results, bodies, API keys, raw domains, raw UUIDs, tokens, passwords, JSON-RPC body/ID, arbitrary error messages, stacks and unknown fields.

School and installation identities use HMAC-SHA256; installation identities are tenant-scoped. The HMAC key and dashboard password are read only from systemd LoadCredential files:

  • telemetry_hmac_key

  • analytics_password

Do not store these values in .env, the unit file, process arguments, Git or logs. Increment TELEMETRY_IDENTITY_EPOCH whenever the HMAC key is rotated.

Storage

  • SQLite: /var/lib/edvibe-mcp/telemetry.sqlite by default.

  • WAL, prepared statements, busy_timeout, private 0700 directories and 0600 files.

  • A bounded asynchronous queue accepts events on the HTTP thread; SQLite, journald and dashboard queries run in a dedicated worker thread. Storage failure never changes an MCP response.

  • Detailed events use a hard 90-day maximum. Identity-free hourly/daily aggregates use a hard 365-day maximum; cleanup runs daily.

  • Daily retention, incremental vacuum and at most seven local backups. Retained backup databases are pruned to the same detail/aggregate cutoffs.

  • The same allow-listed events are written as one JSON line to stderr/journald. Journald is not the annual analytics source; activation must verify a host journald retention policy no longer than 90 days.

Dashboard

When separately enabled, the Russian internal dashboard is available at GET /admin/analytics. It uses Basic Auth with the fixed username analytics and a password from the systemd credential. Its API is read-only:

  • GET /admin/analytics/api/dashboard

  • GET /admin/analytics/api/events

Responses use Cache-Control: no-store, a restrictive CSP, frame denial and no external scripts. HTTPS forwarded by a proxy is trusted only from loopback/private proxy addresses, so port 9000 must remain firewalled from public access. Time is stored in UTC and displayed in Europe/Moscow. Identity drill-down is limited to the 90-day detail window; annual analytics uses identity-free aggregates.

The relevant non-secret flags are:

Variable

Default

Purpose

TELEMETRY_ENABLED

false

Collect safe HTTP events

ANALYTICS_DASHBOARD_ENABLED

false

Serve the protected dashboard/API

TELEMETRY_IDENTITY_EPOCH

1

Pseudonym generation epoch

TELEMETRY_DATABASE_PATH

/var/lib/edvibe-mcp/telemetry.sqlite

SQLite path

TELEMETRY_BACKUP_DIRECTORY

/var/lib/edvibe-mcp/backups

Local backup directory

See deploy/systemd/telemetry.conf.example. The example intentionally keeps both feature flags off and the LoadCredential/StateDirectory directives commented for the first rollout. Provision the private state directory and credential files, then uncomment those directives only during the separately approved activation stage.

Security status

Implemented in this change:

  • fixed safe error taxonomy; no raw upstream body in exceptions;

  • process-keyed HMAC identifiers in the rate-limiter registry instead of raw API keys;

  • bounded limiter/DNS registries with expiry;

  • public-address DNS validation with the validated IPv4 result pinned into the upstream connection, plus a bounded response body;

  • strict telemetry allowlist and fail-open storage;

  • pseudonymous analytics and protected read-only UI.

Still separate from this change:

  • an Edvibe-controlled tenant/White Label allowlist before sending Authorization upstream;

  • actual public disablement of LoginPupil and LoginTeacher until product/security approval.

Repository layout

manifest/                 authoritative 78-operation inventory
openapi/                  immutable snapshot and generated normalized copy
scripts/                  contract generation, validation and deployment helper
src/                      MCP transports, upstream client and telemetry runtime
src/telemetry/            schema, identities, bounded queue, worker-thread SQLite store and queries
test/                     node:test runtime and privacy checks
web/                      landing page and self-contained analytics UI
deploy/systemd/           non-secret systemd drop-in example
README.md CONTEXT.md PLAN.md AGENTS.md

Generated OpenAPI and manifest files must not be edited manually. See CONTEXT.md for decisions, PLAN.md for rollout gates and AGENTS.md for repository rules.

Rollout boundary

The safe sequence is: deploy code with both flags false; verify /healthz and the 78-tool contract; inspect Node/systemd/disk/firewall/proxy logging read-only; only after separate approval provision credentials and private directories, enable collection, run one controlled read-only canary and observe it for 24 hours.

Rollback is to set both feature flags to false and restart the service. The MCP continues to work and the existing database remains private. This repository change does not itself commit, push or deploy anything.

Available Tools

78 tools
AccessGroupsAddMembersC

Add members to an access group.

Inputs: accessGroupId (required), bodyTeacherIds, bodyGroupClassIds, bodyIndividualClassIds.

AccessGroupsCreateA

Create a new access group.

Inputs: name (required).

AccessGroupsDeleteA
Destructive

Delete an access group.

WARNING: Destructive: removes the access group. Requires client-side approval.

Inputs: accessGroupId (required).

AccessGroupsGetDetailsA
Read-only

Get details of a single access group by id.

Inputs: accessGroupId (required).

AccessGroupsGetGroupClassesA
Read-only

List group classes linked to an access group (paginated).

Inputs: accessGroupId (required), Skip, Take.

AccessGroupsGetIndividualClassesA
Read-only

List individual classes linked to an access group (paginated).

Inputs: accessGroupId (required), Skip, Take.

AccessGroupsGetListB
Read-only

List access groups with optional filters by teacher, group class, or individual class.

Inputs: teacherId, groupClassId, individualClassId.

AccessGroupsGetTeachersA
Read-only

List teachers assigned to an access group (paginated).

Inputs: accessGroupId (required), Skip, Take.

AccessGroupsRemoveMembersA
Destructive

Remove members from an access group.

WARNING: Destructive: unlinks members from the group. Requires client-side approval.

Inputs: accessGroupId (required), bodyTeacherIds, bodyGroupClassIds, bodyIndividualClassIds.

BooksGetBookA
Read-only

Get a single book by id.

Inputs: bodyBookId.

BooksGetBooksPlatformA
Read-only

List books available on the platform.

Inputs: no named parameters.

BooksGetBooksSchoolA
Read-only

List books available to the current school.

Inputs: no named parameters.

BooksPinLessonToClassC

Pin a lesson from a book to a class.

Inputs: bodyClassId, bodyLessonId.

ClassesGetStatisticsD
Read-only

Get class statistics.

Inputs: bodyClassId, bodyPupilId.

GroupClassesChangeTeacherC

Change the teacher assigned to a group class.

Inputs: classId (required), teacherId (required).

GroupClassesCreateC

Create a new group class.

Inputs: bodyName (required), bodyTeacherId (required), bodyAccessGroups, bodyLessonTariffDurationId.

GroupClassesDeleteA
Destructive

Delete a group class.

WARNING: Destructive: removes the group class. Requires client-side approval.

Inputs: classId.

GroupClassesGetDetailA
Read-only

Get details of a single group class by id.

Inputs: classId.

GroupClassesGetListB
Read-only

List group classes with optional filters and pagination.

Inputs: Search, PupilId, pageSkip, pageTake, +2 more.

GroupClassesUpdateC

Update an existing group class.

Inputs: classId (required), bodyName, bodyTeacherId, bodyLessonTariffDurationId.

GroupClassPupilsAddA

Add pupils to a group class. Adding a pupil to a group class activates the pupil and consumes the school's tariff limit of activated students. The limit depends on the school's tariff plan. Exceeding it returns HTTP 400 "The number of activated students has been exceeded".

Inputs: classId (required), bodyPupilIds.

GroupClassPupilsDeleteA
Destructive

Remove a pupil from a group class.

WARNING: Destructive: unlinks the pupil from the group class. Requires client-side approval.

Inputs: classId (required), pupilId (required).

GroupClassPupilsGetListA
Read-only

List pupils in a group class (paginated).

Inputs: ClassId (required), pageSkip, pageTake.

IndividualClassesChangeTeacherA

Change the teacher assigned to an individual class.

Inputs: classId (required), teacherId (required), classLearningLanguage.

IndividualClassesCreateC

Create a new individual class.

Inputs: bodyName (required), bodyPupilId (required), bodyTeacherId (required), bodyClassLanguageId.

IndividualClassesDeleteA
Destructive

Delete an individual class.

WARNING: Destructive: removes the individual class. Requires client-side approval.

Inputs: classId.

IndividualClassesGetDetailA
Read-only

Get details of a single individual class by id.

Inputs: classId.

IndividualClassesGetListA
Read-only

List individual classes with optional filters and pagination.

Inputs: Search, PupilId, pageSkip, pageTake, +2 more.

IndividualClassesUpdateB

Update an existing individual class.

Inputs: classId (required), bodyName, bodyLessonTariffDurationId.

LessonPackagesGetListB
Read-only

List lesson packages for a pupil and/or class.

Inputs: classId (required), pupilId (required), updatedAfter.

LessonPackagesSetPackageC

Set a lesson package for a pupil and class.

Inputs: classId (required), pupilId (required), bodyComment (required), bodyLessonPackageId (required), +4 more.

LessonPackagesUpdatePackagePeriodC

Update the period of a lesson package.

Inputs: classId (required), pupilId (required), bodyLessonPackageDurationId (required), bodyToDate, +1 more.

LessonPackagesWriteOffLessonsA
Destructive

Write off lessons from a package.

WARNING: Destructive: decrements lesson balance. Requires client-side approval.

Inputs: bodyCount (required), bodyClassId (required), bodyComment (required), bodyPupilId (required), +1 more.

LessonTariffsAddTariffDurationB

Add a duration option to a lesson tariff.

Inputs: bodyDuration (required), lessonTariffId (required), bodyPrice, bodyLessonsCount, +1 more.

LessonTariffsCreateB

Create a new lesson tariff.

Inputs: bodyName (required), bodyDurations, bodyDescription.

LessonTariffsDeleteA
Destructive

Delete a lesson tariff.

WARNING: Destructive: removes the tariff. Requires client-side approval.

Inputs: lessonTariffId (required).

LessonTariffsDeleteLessonPackageA
Destructive

Delete a lesson package attached to a tariff.

WARNING: Destructive: removes the lesson package. Requires client-side approval.

Inputs: LessonTariffId (required), LessonTariffPackageId (required), LessonTariffDurationId (required).

LessonTariffsDeleteTariffDurationA
Destructive

Delete a tariff duration.

WARNING: Destructive: removes the tariff duration. Requires client-side approval.

Inputs: LessonTariffId (required), LessonTariffDurationId (required).

LessonTariffsGetListA
Read-only

List lesson tariffs (paginated, with soft-deleted filter).

Inputs: Search, pageSkip, pageTake, IsDeleted.

LessonTariffsGetTariffDurationListB
Read-only

List lesson tariff durations (paginated).

Inputs: pageSkip, pageTake, LessonTariffIds.

LessonTariffsGetTariffForDurationIdA
Read-only

Get a lesson tariff by its duration id.

Inputs: schoolLessonTariffDurationId (required).

MarathonAddMarathonNewStudentsA

Add new students to a marathon by email. Note: this is a GET that mutates state.

WARNING: Mutates state: invites new students to a marathon. Requires client-side approval.

Inputs: Emails, MarathonId, ModeratorsIds, MailMessageLanguageId.

MarathonChangeActivationMarathonPupilA
Destructive

Activate or deactivate a pupil in a marathon.

WARNING: Destructive: changes pupil activation state in a marathon. Requires client-side approval.

Inputs: bodyPupilId, bodyIsActivate, bodyMarathonId.

MarathonCreateModeratorC

Create a marathon moderator.

Inputs: bodyTeacherId, bodyMarathonId.

MarathonDeleteModeratorA
Destructive

Delete a marathon moderator.

WARNING: Destructive: removes the moderator. Requires client-side approval.

Inputs: bodyMarathonId, bodyIsSelectAll, bodyModeratorId, bodySelectedPupilsIds.

MarathonGetMarathonListA
Read-only

List marathons (paginated, with search and folder filter).

Inputs: FolderId, pageSkip, pageTake, SearchTerm.

MarathonGetMarathonStudentsA
Read-only

List students in a marathon with search and filter.

Inputs: Filter, MarathonId, SearchTerm.

MarathonGetModeratorsA
Read-only

List marathon moderators (paginated).

Inputs: pageSkip, pageTake, TeacherId, MarathonId.

MarathonSetModeratorsForPupilB

Assign moderators to a marathon pupil.

Inputs: bodyMarathonPupilId, bodySelectedModeratorsIds.

MarathonSetPupilsForModeratorB

Assign pupils to a marathon moderator.

Inputs: bodyMarathonId, bodyModeratorId, bodySelectedPupilsIds.

MarathonUnsetModeratorsForPupilA
Destructive

Unassign moderators from a marathon pupil.

WARNING: Destructive: unlinks moderators from the pupil. Requires client-side approval.

Inputs: bodyMarathonPupilId, bodySelectedModeratorsIds.

MarathonUnsetPupilsForModeratorB
Destructive

Unassign pupils from a marathon moderator.

WARNING: Destructive: unlinks pupils from the moderator. Requires client-side approval.

Inputs: bodyMarathonId, bodyIsSelectAll, bodyModeratorId, bodySelectedPupilsIds.

PupilsCreateC

Create a new pupil.

Inputs: bodyName (required), bodyEmail (required), bodyNote, bodyPhone, +12 more.

PupilsDeleteA
Destructive

Delete a pupil. The pupil must be unlinked from all classes first: remove from group classes via GroupClassPupilsDelete and delete individual classes via IndividualClassesDelete. Upstream returns HTTP 400 "Pupil have classes" if any class link remains.

WARNING: Destructive: removes the pupil. Requires client-side approval.

Inputs: pupilId.

PupilsGetCursorListB
Read-only

List pupils using cursor-based pagination.

Inputs: Search, ActiveOnly, OnlyUpdated, cursorLimit, +6 more.

PupilsGetDetailA
Read-only

Get details of a single pupil by id.

Inputs: pupilId.

PupilsGetListA
Read-only

List pupils with search, filters and pagination.

Inputs: Search, pageSkip, pageTake, ActiveOnly, +4 more.

PupilsUpdateB

Update an existing pupil.

Inputs: pupilId (required), bodyName, bodyNote, bodyEmail, +12 more.

PupilTagCreateB

Create a new pupil tag.

Inputs: TagName (required).

PupilTagDeleteA
Destructive

Delete a pupil tag.

WARNING: Destructive: removes the tag. Requires client-side approval.

Inputs: TagId (required).

PupilTagGetListA
Read-only

List all pupil tags.

Inputs: no named parameters.

ScheduleCreateLessonA

Create a new lesson in the schedule. The groupId field accepts the id of any class (group or individual). Creating a lesson may activate the pupil as a side effect (response includes isActivatedPupil).

Inputs: bodyDate (required), bodyGroupId (required), bodyDurationMinutes (required), bodyPupilId, +3 more.

ScheduleDeleteLessonA
Destructive

Delete a lesson from the schedule.

WARNING: Destructive: removes the lesson. Requires client-side approval.

Inputs: lessonId, bodyComment, bodyDeleteRepeatLessons.

ScheduleGetPackageForGroupLessonB
Read-only

Get the lesson package applicable to a group lesson.

Inputs: lessonId (required), pupilId.

ScheduleGetPackageForIndividualLessonA
Read-only

Get the lesson package applicable to an individual lesson.

Inputs: lessonId (required).

ScheduleGetPupilScheduleA
Read-only

Get a pupil's schedule for a date range.

Inputs: dateEnd (required), pupilId (required), dateStart (required), bodyGroupId, +2 more.

ScheduleGetSchoolScheduleB
Read-only

Get the school schedule for a date range.

Inputs: bodyPage, bodyDateEnd, bodyGroupId, bodyPupilId, +3 more.

ScheduleGetSchoolWeekWorkTimeA
Read-only

Get the school's weekly work time.

Inputs: no named parameters.

ScheduleGetTeacherScheduleA
Read-only

Get a teacher's schedule for a date range in a given timezone.

Inputs: dateEnd (required), dateStart (required), teacherId (required), timezone.

ScheduleGetTeacherWeekWorkTimeB
Read-only

Get a teacher's weekly work time.

Inputs: teacherId (required), fromMonday (required).

TeachersCreateB

Create a new teacher.

Inputs: bodyName (required), bodyEmail (required), bodyPhone, bodySkype, +7 more.

TeachersDeleteA
Destructive

Delete a teacher.

WARNING: Destructive: removes the teacher. Requires client-side approval.

Inputs: teacherId.

TeachersGetDetailA
Read-only

Get details of a single teacher by id.

Inputs: teacherId.

TeachersGetListB
Read-only

List teachers with search and pagination.

Inputs: Search, pageSkip, pageTake.

TeachersUpdateC

Update an existing teacher.

Inputs: bodyName, bodyEmail, bodyPhone, bodySkype, +9 more.

UserAuthCheckAuthTokenC
Read-only

Check whether an auth token is valid.

Inputs: bodySessionToken.

UserAuthLoginPupilA

Authenticate a pupil and return a login token.

WARNING: Sensitive: returns a login token. Public enablement requires recorded product/security approval. The token must never be logged, cached, or returned outside the direct successful tool result.

Inputs: bodyPupilId.

UserAuthLoginTeacherA

Authenticate a teacher and return a login token.

WARNING: Sensitive: returns a login token. Public enablement requires recorded product/security approval. The token must never be logged, cached, or returned outside the direct successful tool result.

Inputs: bodyTeacherId.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.9/5.0

    Scored across 78 tools

    Disambiguation4/5

    Most tools are clearly separated by resource prefix and action, and the descriptions clarify their purpose. A few list-related tools such as PupilsGetList vs PupilsGetCursorList and LessonTariffsGetTariffForDurationId vs LessonTariffsGetTariffDurationList could cause misselection, but overall boundaries are reasonably clear.

    Naming Consistency4/5

    The tools generally follow a Resource + Verb + OptionalObject pattern, making them readable and predictable. Deviations such as MarathonAddMarathonNewStudents, MarathonChangeActivationMarathonPupil, and mixed Create/Add/Set/Unset verbs are minor inconsistencies within an otherwise coherent scheme.

    Tool Count1/5

    78 tools is an extreme count for a single MCP server. While the domain is broad, the surface area is far too large for effective agent discovery and selection, and many tools could be grouped or exposed as sub-resources rather than flat tool entries.

    Completeness3/5

    The server covers many core school-management workflows: pupils, teachers, group/individual classes, tariffs, packages, schedules, and auth. However, there are notable gaps such as no marathon create/update/delete, no schedule lesson update/reschedule, and no access group update, which leave some workflows incomplete.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      This MCP server provides comprehensive read and write access to Google Classroom, enabling management of courses, rosters, assignments, and grades. It supports full lifecycle operations for educational environments including student submissions, teacher invitations, and coursework organization.
      -
    • F
      license
      Not graded
      quality
      D
      maintenance
      An MCP server that enables AI agents to safely query and act on school data (attendance, fees, student records) with strict role-based access control and a two-step write approval flow.
      -
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables LLM agents and automation tools to interact with Moodle through a permission-checked MCP endpoint, covering courses, activities, question banks, enrolments, and administrative operations via 240 external functions.
      4
      GPL 3.0
    • F
      license
      C
      quality
      C
      maintenance
      Enables interaction with the Edookit school management system API, exposing attendance, lessons, people, grades, payments, courses, timetable, and individual goals as MCP tools.
      46
      -