MCP & OAuth 2.1

What the MCP server offers

Institflow runs a Model Context Protocol server at /mcp (Streamable HTTP, stateless — every call is independent, so it scales like any other API request). It exposes the same domain services every REST endpoint calls, through 51 task-oriented tools, a handful of read-only resources, and a few report-style prompts — so an AI agent (Claude, or any MCP client) can search students, check a schedule, record attendance, or draft a progress report, using exactly the caller's own permissions, never more.

Connecting a client

Institflow is its own OAuth 2.1 authorization server for this — no separate API key or manual token needed. A compliant MCP client discovers everything it needs automatically:

  1. It fetches GET /.well-known/oauth-protected-resource (RFC 9728) from /mcp's WWW-Authenticate challenge, then GET /.well-known/oauth-authorization-server (RFC 8414) for the token/registration/authorize endpoints.
  2. If it doesn't already have a client_id, it registers one dynamically: POST /oauth/register (RFC 7591 — a public client, no secret; disable this endpoint tenant-wide with the OAUTH_DYNAMIC_REGISTRATION setting if you'd rather pre-register clients yourself).
  3. It opens GET /oauth/authorize with PKCE (S256 — required, not optional, under OAuth 2.1) in your browser. You sign in (if needed) and land on a consent screen showing exactly which tenant/membership you're connecting and what the client can do.
  4. After you approve, the client exchanges the authorization code — POST /oauth/token with your PKCE code_verifier — for an access token (a normal Institflow JWT, just typ: "mcp") and a refresh token good for 30 days.
mcp request
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9...

An MCP token never authenticates a REST call

The reverse also isn't possible — a typ: "mcp" token is rejected with 401 on every ordinary /api/v1/* route, and a regular access token can't call /mcp. They're deliberately separate.

Scopes & permissions

Getting a token at all requires the mcp.access permission on the membership you connect with — a member without it (most secretary/teacher roles, by default) is refused at the consent step, before any token is ever issued. Every tool additionally checks its own declared permission on every call — identical to what the equivalent REST endpoint would require.

Tools marked destructive below need mcp.destructive as well. Calling one without confirm: true never mutates anything — it returns a preview of what would happen (e.g. "would change status from active to withdrawn") and requiresConfirmation: true. The agent (or the person driving it) re-calls the same tool with confirm: true to actually execute it. Every call — preview or execution — is audited with channel: "mcp", the tool name, and a hash of its arguments (never the raw arguments, which may carry PII).

Tool catalogue

Every tool, generated directly from apps/api/src/modules/mcp/tools/*.ts — the exact source the MCP server itself registers at boot.

Academics

ToolDescriptionPermissionDestructive
cancel_sessionCancel a scheduled class session, with a reason. Destructive: previews unless confirm: true is passed.groups.writeYes
create_groupCreate a new teaching group (class section) for a course, with an optional weekly schedule.groups.writeNo
drop_enrollmentDrop a student's enrollment from a group. Destructive: previews unless confirm: true is passed.enrollments.writeYes
enroll_studentEnroll a student into a teaching group.enrollments.writeNo
get_groupGet full details for one teaching group by id.groups.readNo
get_scheduleGet scheduled class sessions for a group, teacher, or student within an optional date range. Exactly one of groupId/teacherStaffId/studentId is required.groups.readNo
list_coursesList the course catalogue (subjects/programs offered), optionally filtered by category or active status.courses.readNo
list_groupsList teaching groups (class sections), optionally filtered by branch, course, status, or teacher.groups.readNo
transfer_studentTransfer a student's enrollment from their current group to another group (drop + enroll in one operation). Destructive: previews unless confirm: true is passed.enrollments.writeYes

Admin

ToolDescriptionPermissionDestructive
dashboard_summaryOwner/manager dashboard summary — currently the tenant-wide billing summary (outstanding balance, collections, overdue).invoices.readNo
get_settingsGet the tenant's effective settings (general, attendance, grading, billing, notifications).settings.manageNo
invite_memberInvite a new member to the tenant by email, with a role. Destructive: previews unless confirm: true is passed.members.manageYes
list_membersList members of the current tenant, with their assigned roles.members.manageNo
list_rolesList every role (system and custom) in the tenant, with its permissions.roles.manageNo
set_member_rolesSet a member's complete role assignment (within one branch scope, or tenant-wide) to exactly the given list of role ids. Destructive: previews the add/remove diff unless confirm: true is passed.members.manageYes
update_settingsUpdate one or more tenant settings keys. Destructive: previews the before/after values unless confirm: true is passed.settings.manageYes

Assessment

ToolDescriptionPermissionDestructive
create_examCreate a new exam/quiz/homework/project for a group.assessment.writeNo
generate_progress_reportQueue generation of a progress report for a student or an entire group over a period. Asynchronous — returns immediately once queued.assessment.writeNo
get_student_gradesGet a student's grades (per-group weighted average and per-exam breakdown), optionally scoped to one group.assessment.readNo
publish_examPublish an exam so its results become visible to students/guardians. Destructive: previews unless confirm: true is passed.assessment.writeYes
record_resultsBulk-record (or update) student scores for an exam.assessment.writeNo
send_progress_reportsSend every published (not-yet-sent) progress report for a student or group to guardians. Destructive: previews the list unless confirm: true is passed.assessment.writeYes

Attendance

ToolDescriptionPermissionDestructive
attendance_reportAttendance report for a group (per-student rates) or a single student (full history), within an optional date range.attendance.readNo
get_session_rosterGet the attendance roster for a scheduled session — every enrolled student and their current attendance mark, if any.attendance.readNo
record_attendanceBulk-record (or update) attendance marks for every student on a session's roster.attendance.markNo

Billing

ToolDescriptionPermissionDestructive
billing_summaryTenant-wide billing summary — outstanding balance, this month's collections, and overdue counts/totals.invoices.readNo
create_invoiceCreate a new draft invoice for a student, with one or more line items.invoices.writeNo
get_invoiceGet full details for one invoice, including its lines and payments.invoices.readNo
list_invoicesList invoices, filterable by status (including an "overdue" shorthand), student, guardian, or due-date range.invoices.readNo
record_paymentRecord a payment against an invoice.payments.recordNo
student_statementGet a student's full billing statement — every invoice/payment entry and the running balance.invoices.readNo
void_invoiceVoid an invoice, with a reason. Destructive: previews unless confirm: true is passed.invoices.writeYes

curriculum

ToolDescriptionPermissionDestructive
create_lessonCreate a lesson in a course. Omit sectionId to use (or auto-create) the course’s default section.curriculum.writeNo
delete_lessonDelete a lesson and its video and progress rows. Destructive: previews (with how many students have progress on it) unless confirm: true is passed.curriculum.writeYes
get_lessonGet one lesson by id, including its markdown body and video source.curriculum.readNo
grant_course_accessGrant a student access to a self-paced (video) course. Idempotent — granting twice returns the existing enrollment.enrollments.writeNo
list_course_curriculumList a course’s curriculum: its sections, each with the lessons inside it. Published lessons only unless includeDrafts is true.curriculum.readNo
publish_lessonPublish a lesson so enrolled students can see it. Destructive: notifies real people, so it previews unless confirm: true is passed.curriculum.publishYes
revoke_course_accessRevoke a student’s self-paced course access. Destructive: previews unless confirm: true is passed.enrollments.writeYes
set_lesson_videoAttach a video to a lesson from a YouTube or TeraBox share URL. The URL is validated and normalized exactly as the web editor does.curriculum.writeNo
student_course_progressOne student’s progress through their video courses — lessons completed vs. available, and the last lesson they opened.curriculum.readNo
update_lessonUpdate a lesson’s title, summary, markdown body, estimate or release rules.curriculum.writeNo

Notifications

ToolDescriptionPermissionDestructive
notification_usageNotification channel usage/cost for a billing period (defaults to the current month, format YYYY-MM).notifications.readNo
send_messageSend a message to a student/guardian/group/branch audience via the notification channel ladder (email/SMS/WhatsApp/push, falling back down the ladder). SMS/WhatsApp are platform/licence-approved per tenant — naming one explicitly in `channels` when it is not approved is rejected; leaving `channels` unset falls through the ladder to an approved channel (in-app always reachable) instead. Destructive: previews recipient count and quota impact unless confirm: true is passed.notifications.sendYes

Students

ToolDescriptionPermissionDestructive
add_guardianAttach a guardian to a student — either link an existing guardian by id, or create a new one and link it.guardians.writeNo
create_studentCreate a new student record in the current tenant.students.writeNo
get_studentGet full details for one student by id, including guardians and status.students.readNo
search_studentsSearch/list students in the current tenant, filtered by free-text query, status, branch, group, or tag. Cursor-paginated.students.readNo
set_student_statusChange a student's lifecycle status (lead/active/inactive/graduated/withdrawn). Destructive: previews the transition unless confirm: true is passed.students.writeYes
update_studentUpdate a student's profile fields (name, contact info, tags, ...). Does not change status — use set_student_status for that.students.writeNo

Resources & prompts

Read-only resources, addressed by URI, for a client that wants to load context without calling a tool:

URIReturns
institflow://student/{id}A student's profile, guardians, and status.
institflow://group/{id}A teaching group's schedule and roster.
institflow://invoice/{id}An invoice with its lines and payments.
institflow://schedule/todayToday's sessions across the tenant (capped at 100).

Prompts — reusable instruction templates a client can surface as a slash command or button:

  • monthly_report (args: groupId or studentId, month)
  • overdue_followup — draft a follow-up plan for overdue invoices.
  • daily_briefing — today's schedule, absences, and anything overdue, summarized.