## ما الذي يقدّمه خادم MCP

يُشغّل Institflow خادم [Model Context Protocol](https://modelcontextprotocol.io) على المسار `/mcp` (بروتوكول Streamable HTTP، بلا حالة — كل استدعاء مستقلّ، فيتوسّع تمامًا كأي طلب واجهة آخر). يعرض نفس خدمات النطاق التي تستدعيها كل نقطة نهاية REST، عبر 74 أداة موجَّهة بالمهام، وعددًا من الموارد للقراءة فقط، وبضعة مطالبات (prompts) بأسلوب التقارير — بحيث يستطيع وكيل ذكاء اصطناعي (Claude، أو أي عميل MCP) البحث عن طلاب، أو الاطّلاع على جدول، أو تسجيل حضور، أو صياغة تقرير تقدّم، مستخدِمًا **صلاحيات المستدعي نفسه بدقّة**، لا أكثر أبدًا.

## ربط عميل (Client)

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`. الفصل بينهما مقصود.

## الصلاحيات والنطاقات

الحصول على رمز أصلًا يتطلّب صلاحية `mcp.access` على العضوية التي تربط بها — عضو بلا هذه الصلاحية (معظم أدوار `secretary`/`teacher` افتراضيًا) يُرفَض عند خطوة الموافقة، قبل إصدار أي رمز. كل أداة تتحقّق أيضًا من صلاحيتها الخاصة المُعلَنة عند كل استدعاء — مطابقة تمامًا لما تتطلّبه نقطة نهاية REST المكافئة.

الأدوات المُعلَّمة **كإجراء حسّاس** أدناه تحتاج أيضًا إلى `mcp.destructive`. استدعاء إحداها بلا `confirm: true` لا يُعدّل شيئًا أبدًا — يُعيد معاينة لما *سيحدث* (مثل: "سيغيّر الحالة من نشط إلى منسحب") مع `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). |

مطالبات — قوالب تعليمات جاهزة لإعادة الاستخدام يمكن لعميل عرضها كأمر سريع أو زرّ:

- `monthly_report` (المعطيات: `groupId` أو `studentId`، و`month`)
- `overdue_followup` — صياغة خطّة متابعة للفواتير المتأخّرة.
- `daily_briefing` — ملخّص جدول اليوم والغياب وأي تأخّر في السداد.
