## מה שרת ה-MCP מציע

Institflow מריצה שרת [Model Context Protocol](https://modelcontextprotocol.io) בכתובת `/mcp` (Streamable HTTP, חסר מצב — כל קריאה עצמאית, ולכן הוא מתרחב כמו כל בקשת API אחרת). הוא חושף את אותם שירותי דומיין שכל נקודת קצה של REST קוראת להם, דרך 74 כלים מוכווני משימה, קומץ משאבים לקריאה בלבד וכמה prompts בסגנון דוחות — כך שסוכן AI (Claude, או כל לקוח MCP) יכול לחפש תלמידים, לבדוק לוח זמנים, לרשום נוכחות או לנסח דוח התקדמות, תוך שימוש **בהרשאות של הקורא עצמו בדיוק**, לעולם לא יותר.

## חיבור לקוח

Institflow היא שרת ההרשאות של OAuth 2.1 עבור זה — אין צורך במפתח API נפרד או בטוקן ידני. לקוח MCP תקני מגלה לבד את כל מה שהוא צריך:

1. הוא מביא `GET /.well-known/oauth-protected-resource` (RFC 9728) מתוך אתגר ה-`WWW-Authenticate` של `/mcp`, ואז `GET /.well-known/oauth-authorization-server` (RFC 8414) לנקודות הקצה של הטוקן, הרישום וההרשאה.
2. אם עדיין אין לו `client_id`, הוא רושם אחד דינמית: `POST /oauth/register` (RFC 7591 — לקוח ציבורי, בלי סוד; אפשר לכבות את נקודת הקצה הזו לכל הדייר בהגדרה `OAUTH_DYNAMIC_REGISTRATION` אם אתם מעדיפים לרשום לקוחות מראש בעצמכם).
3. הוא פותח `GET /oauth/authorize` עם PKCE (S256 — חובה, לא רשות, תחת OAuth 2.1) בדפדפן שלכם. אתם מתחברים (אם צריך) ומגיעים למסך הסכמה שמראה בדיוק לאיזה דייר/חברות אתם מתחברים ומה הלקוח יכול לעשות.
4. אחרי שאישרתם, הלקוח מחליף את קוד ההרשאה — `POST /oauth/token` עם ה-`code_verifier` של PKCE — בטוקן גישה (JWT רגיל של Institflow, רק עם `typ: "mcp"`) ובטוקן רענון שתקף ל-30 יום.

```http
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9...
```

> **טוקן MCP לעולם אינו מאמת קריאת REST**
>
> גם ההפך אינו אפשרי — טוקן עם `typ: "mcp"` נדחה עם `401` בכל מסלול `/api/v1/*` רגיל, וטוקן גישה רגיל אינו יכול לפנות אל `/mcp`. ההפרדה מכוונת.

## הרשאות ו-Scopes

עצם קבלת טוקן דורשת את ההרשאה `mcp.access` על החברות שאיתה אתם מתחברים — חבר צוות שאין לו אותה (רוב תפקידי `secretary`/`teacher` כברירת מחדל) נדחה בשלב ההסכמה, לפני שהונפק טוקן כלשהו. בנוסף, כל כלי בודק את ההרשאה המוצהרת שלו בכל קריאה — זהה למה שנקודת הקצה המקבילה ב-REST הייתה דורשת.

כלים המסומנים למטה כ**הרסניים** דורשים גם `mcp.destructive`. קריאה לכלי כזה בלי `confirm: true` לעולם אינה משנה דבר — היא מחזירה תצוגה מקדימה של מה *היה* קורה (למשל “היה משנה את הסטטוס מ-active ל-withdrawn”) ואת `requiresConfirmation: true`. הסוכן (או האדם שמפעיל אותו) קורא שוב לאותו כלי עם `confirm: true` כדי לבצע בפועל. כל קריאה — תצוגה מקדימה או ביצוע — נרשמת ביומן הביקורת עם `channel: "mcp"`, שם הכלי, וגיבוב של הארגומנטים שלו (לעולם לא הארגומנטים הגולמיים, שעשויים להכיל מידע אישי).

## קטלוג הכלים

כל הכלים, נוצרים ישירות מ-`apps/api/src/modules/mcp/tools/*.ts` — בדיוק המקור ששרת ה-MCP עצמו רושם באתחול.

| כלי | תיאור | הרשאה | פעולה הרסנית |
| --- | --- | --- | --- |
| cancel_session | Cancel a scheduled class session, with a reason. Destructive: previews unless confirm: true is passed. | groups.write | כן |
| create_group | Create a new teaching group (class section) for a course, with an optional weekly schedule. | groups.write | לא |
| drop_enrollment | Drop a student's enrollment from a group. Destructive: previews unless confirm: true is passed. | enrollments.write | כן |
| enroll_student | Enroll a student into a teaching group. | enrollments.write | לא |
| get_group | Get full details for one teaching group by id. | groups.read | לא |
| get_schedule | Get scheduled class sessions for a group, teacher, or student within an optional date range. Exactly one of groupId/teacherStaffId/studentId is required. | groups.read | לא |
| list_courses | List the course catalogue (subjects/programs offered), optionally filtered by category or active status. | courses.read | לא |
| list_groups | List teaching groups (class sections), optionally filtered by branch, course, status, or teacher. | groups.read | לא |
| transfer_student | Transfer a student's enrollment from their current group to another group (drop + enroll in one operation). Destructive: previews unless confirm: true is passed. | enrollments.write | כן |
| audit_search | Search the tenant's audit log by action, entity, actor, or date range. | audit.read | לא |
| dashboard_summary | Owner/manager dashboard summary — currently the tenant-wide billing summary (outstanding balance, collections, overdue). | invoices.read | לא |
| get_settings | Get the tenant's effective settings (general, attendance, grading, billing, notifications, curriculum, reminders). | settings.manage | לא |
| invite_member | Invite a new member to the tenant by email, with a role. Destructive: previews unless confirm: true is passed. | members.manage | כן |
| list_members | List members of the current tenant, with their assigned roles. | members.manage | לא |
| list_roles | List every role (system and custom) in the tenant, with its permissions. | roles.manage | לא |
| set_member_roles | Set 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.manage | כן |
| update_settings | Update one or more tenant settings keys. Destructive: previews the before/after values unless confirm: true is passed. | settings.manage | כן |
| create_exam | Create a new exam/quiz/homework/project for a group. | assessment.write | לא |
| generate_progress_report | Queue generation of a progress report for a student or an entire group over a period. Asynchronous — returns immediately once queued. | assessment.write | לא |
| get_student_grades | Get a student's grades (per-group weighted average and per-exam breakdown), optionally scoped to one group. | assessment.read | לא |
| publish_exam | Publish an exam so its results become visible to students/guardians. Destructive: previews unless confirm: true is passed. | assessment.write | כן |
| record_results | Bulk-record (or update) student scores for an exam. | assessment.write | לא |
| send_progress_reports | Send every published (not-yet-sent) progress report for a student or group to guardians. Destructive: previews the list unless confirm: true is passed. | assessment.write | כן |
| curriculum.assignments.create | Create an assignment unit in a course: a lesson of kind "assignment" plus its instructions, due date, max score, late policy and allowed submission kinds, in one transaction. | curriculum.write | לא |
| curriculum.assignments.grade_submission | Grade a submission (score at most the assignment’s max score, optional feedback). Emits assignment.graded. | curriculum.assignments.grade | לא |
| curriculum.assignments.list_submissions | List an assignment’s submissions (cursor-paginated), optionally filtered by status. lessonId is the assignment unit’s id, or the id of the video lesson that carries the task. | curriculum.assignments.grade | לא |
| curriculum.assignments.publish_to_gradebook | Publish every currently-graded submission into the gradebook as a "homework" exam per group — upserts on repeat. Destructive: the first publish for a group creates and publishes a real exam, notifying students/guardians, so it previews unless confirm: true is passed. | curriculum.assignments.grade | כן |
| curriculum.assignments.return_submission | Send a submission back to the student for revision (clears any earlier grade). | curriculum.assignments.grade | לא |
| curriculum.assignments.update | Update an assignment’s own fields (instructions, due date, max score, late policy, allowed submission kinds). Lesson-shared fields (title, section, drip gate) go through update_lesson. | curriculum.write | לא |
| curriculum.lesson_tasks.remove | Remove the task under a video lesson. Destructive: previews (with the number of submissions that would be deleted with it) unless confirm: true is passed, which also confirms deleting those submissions. | curriculum.write | כן |
| curriculum.lesson_tasks.set | Create or replace «مهمة هذا الدرس», the task under a video lesson (instructions, due date, max score, late policy, accepted submission kinds). Submissions, grading and the gradebook publish then use the curriculum.assignments.* tools with the same lessonId. | curriculum.write | לא |
| attendance_report | Attendance report for a group (per-student rates) or a single student (full history), within an optional date range. | attendance.read | לא |
| get_session_roster | Get the attendance roster for a scheduled session — every enrolled student and their current attendance mark, if any. | attendance.read | לא |
| record_attendance | Bulk-record (or update) attendance marks for every student on a session's roster. | attendance.mark | לא |
| billing_summary | Tenant-wide billing summary — outstanding balance, this month's collections, and overdue counts/totals. | invoices.read | לא |
| create_invoice | Create a new draft invoice for a student, with one or more line items. | invoices.write | לא |
| get_invoice | Get full details for one invoice, including its lines and payments. | invoices.read | לא |
| list_invoices | List invoices, filterable by status (including an "overdue" shorthand), student, guardian, or due-date range. | invoices.read | לא |
| record_payment | Record a payment against an invoice. | payments.record | לא |
| student_statement | Get a student's full billing statement — every invoice/payment entry and the running balance. | invoices.read | לא |
| void_invoice | Void an invoice, with a reason. Destructive: previews unless confirm: true is passed. | invoices.write | כן |
| create_lesson | Create a lesson in a course. Omit sectionId to use (or auto-create) the course’s default section. audienceGroupIds restricts it to those groups of the course; omit or null for every group. | curriculum.write | לא |
| delete_lesson | Delete a lesson and its video and progress rows. Destructive: previews (with how many students have progress on it) unless confirm: true is passed. | curriculum.write | כן |
| get_lesson | Get one lesson by id, including its markdown body and video source. | curriculum.read | לא |
| grant_course_access | Grant a student access to a self-paced (video) course. Idempotent — granting twice returns the existing enrollment. | enrollments.write | לא |
| list_course_curriculum | List a course’s curriculum: its sections, each with the lessons inside it. Published lessons only unless includeDrafts is true. | curriculum.read | לא |
| publish_lesson | Publish a lesson so enrolled students can see it. Destructive: notifies real people, so it previews unless confirm: true is passed. | curriculum.publish | כן |
| revoke_course_access | Revoke a student’s self-paced course access. Destructive: previews unless confirm: true is passed. | enrollments.write | כן |
| set_lesson_video | Attach a video to a lesson from a YouTube URL. The URL is validated and normalized exactly as the web editor does. | curriculum.write | לא |
| student_course_progress | One student’s progress through their video courses — lessons completed vs. available, and the last lesson they opened. | curriculum.read | לא |
| update_lesson | Update a lesson’s title, summary, markdown body, estimate, release rules or audience (audienceGroupIds: a list of the course’s group ids to restrict the lesson to those groups, null for every group). | curriculum.write | לא |
| get_notification_rule | One notification type: the effective rule, the override the institute made, what the type allows and locks, and per channel whether it can deliver, who bills it and the price per message. | notifications.read | לא |
| get_notification_spend | Notification usage this month per channel and, when the install is on the Institflow messaging relay, the credit balance. | notifications.manage | לא |
| list_notification_rules | The notification rules of the institute: for every kind of notice the system sends — absence, invoices, payments, grades, lessons, assignments, quizzes, class and deadline reminders, subscription alerts — who receives it, on which channels, and whether the institute changed it from the Institflow default. | notifications.read | לא |
| notification_usage | Notification channel usage/cost for a billing period (defaults to the current month, format YYYY-MM). | notifications.read | לא |
| preview_notification_rule | Changes nothing: the estimated monthly cost (a range, in credit units — 10 000 units per credit), how many people a proposed rule would reach, whether saving it needs cost confirmation, and warnings. Omit `rule` to describe the current rule. | notifications.read | לא |
| reset_notification_rule | Deletes the override the institute made for one notification type: it goes back to the Institflow default. Destructive: previews the change unless confirm: true is passed. Going back to the default can switch the type or a paid channel on again; that additionally needs confirmCost: true, exactly like update_notification_rule. | notifications.manage | כן |
| send_message | Send 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.send | כן |
| update_notification_rule | Replaces the override the institute made for one notification type (a field left out goes back to the Institflow default). Destructive: previews before/after AND the estimated cost unless confirm: true is passed. A change that raises what the institute pays for — a paid channel switched on, recipients, lead times or scope added — additionally needs confirmCost: true, so an assistant cannot spend credit by accident even with confirm. | notifications.manage | כן |
| curriculum.quizzes.add_question | Add a question to a quiz. Returns the whole quiz (questions included). | curriculum.quizzes.manage | לא |
| curriculum.quizzes.create | Create a quiz unit in a course: a lesson of kind "quiz" plus its settings, in one transaction. No questions yet. | curriculum.quizzes.manage | לא |
| curriculum.quizzes.delete_question | Delete a quiz question. Returns the whole quiz (questions included). | curriculum.quizzes.manage | לא |
| curriculum.quizzes.grade_answer | Grade a short-answer (short_text) answer (points at most the question’s own points). Finalises the attempt’s score/passed once nothing is left ungraded. Emits quiz.graded on finalisation. | curriculum.assignments.grade | לא |
| curriculum.quizzes.list_attempts | List a quiz’s attempts (cursor-paginated), optionally filtered by student or status. | curriculum.assignments.grade | לא |
| curriculum.quizzes.publish_to_gradebook | Publish each student’s best fully-graded attempt into the gradebook as a "quiz" exam per group — upserts on repeat. Destructive: the first publish for a group creates and publishes a real exam, notifying students/guardians, so it previews unless confirm: true is passed. | curriculum.assignments.grade | כן |
| curriculum.quizzes.reorder_questions | Whole-list reorder of a quiz’s questions. Returns the whole quiz (questions included). | curriculum.quizzes.manage | לא |
| curriculum.quizzes.update | Update a quiz’s own settings (time limit, attempts allowed, shuffle, show-answers-after, pass score, due date). Lesson-shared fields go through update_lesson. | curriculum.quizzes.manage | לא |
| curriculum.quizzes.update_question | Update a quiz question. Returns the whole quiz (questions included). | curriculum.quizzes.manage | לא |
| add_guardian | Attach a guardian to a student — either link an existing guardian by id, or create a new one and link it. | guardians.write | לא |
| create_student | Create a new student record in the current tenant. | students.write | לא |
| get_student | Get full details for one student by id, including guardians and status. | students.read | לא |
| search_students | Search/list students in the current tenant, filtered by free-text query, status, branch, group, or tag. Cursor-paginated. | students.read | לא |
| set_student_status | Change a student's lifecycle status (lead/active/inactive/graduated/withdrawn). Destructive: previews the transition unless confirm: true is passed. | students.write | כן |
| update_student | Update a student's profile fields (name, contact info, tags, ...). Does not change status — use set_student_status for that. | students.write | לא |

## משאבים ו-Prompts

משאבים לקריאה בלבד, ממוענים לפי URI, ללקוח שרוצה לטעון הקשר בלי לקרוא לכלי:

| URI | מחזיר |
| --- | --- |
| `institflow://student/{id}` | פרופיל תלמיד: פרטיו, אפוטרופוסיו ומצבו. |
| `institflow://group/{id}` | לוח הזמנים של קבוצת לימוד ורשימת תלמידיה. |
| `institflow://invoice/{id}` | חשבונית עם השורות והתשלומים שלה. |
| `institflow://schedule/today` | מפגשי היום בכל הדייר (עד 100). |

Prompts — תבניות הנחיה לשימוש חוזר שלקוח יכול להציג כפקודת סלאש או ככפתור:

- `monthly_report` (ארגומנטים: `groupId` או `studentId`, `month`)
- `overdue_followup` — ניסוח תוכנית מעקב לחשבוניות באיחור.
- `daily_briefing` — לוח הזמנים של היום, היעדרויות וכל מה שבאיחור, בתקציר.
