Files
sm-bm-plugin/mcp_student.go
Jakub Zych df42e30034 feat: MCP view of BM answers (absolute links, no material body by default)
- jz.bm.mcp-links on the token twins and the management API: under MCP,
  url and *_url strings starting with a single / get the app base
- material answers under MCP leave the body out unless include_body,
  marking it with body_omitted; write answers never echo it
- jz.bm.manage keys a dispatched call by user
- bm_materials and bm_material student tools
Plain HTTP answers are unchanged.
2026-10-28 14:37:10 +01:00

195 lines
8.0 KiB
Go

package bm
import (
"net/http"
"git.golem15.com/golem15/summercms/modules/pact"
)
// MCPMaterialPrivacy is the sentence every material tool description
// carries (D-17, D-21).
const MCPMaterialPrivacy = "Returns metadata and links only, never file contents; a material's body (possible transcript text) is left out unless include_body is true."
// The sentences every student tool description ends with (D-15).
const (
needsScopeRead = " Needs scope read."
needsScopeWrite = " Needs scope write."
)
// noInput is the input of a tool that takes no arguments.
type noInput struct{}
// idInput names one record by its id.
type idInput struct {
ID uint `json:"id" path:"id" jsonschema:"the record id, as listed by the matching list tool"`
}
// readingIDInput names one reading by its id.
type readingIDInput struct {
ID uint `json:"id" path:"id" jsonschema:"the reading id, as listed by bm_readings or bm_course"`
}
// coursesInput is the input of bm_courses.
type coursesInput struct {
Semester int `json:"semester,omitempty" query:"semester" jsonschema:"only courses of this semester, 1 to 4"`
}
// rangeInput is the input of bm_schedule and bm_deadlines.
type rangeInput struct {
From string `json:"from,omitempty" query:"from" jsonschema:"first day, YYYY-MM-DD (default: today)"`
To string `json:"to,omitempty" query:"to" jsonschema:"last day (inclusive), YYYY-MM-DD, not before from (default: no end)"`
}
// readingsInput is the input of bm_readings.
type readingsInput struct {
Course uint `json:"course,omitempty" query:"course" jsonschema:"only readings of this course id"`
}
// materialsInput is the input of bm_materials.
type materialsInput struct {
Course uint `json:"course,omitempty" query:"course" jsonschema:"only materials of this course id"`
Meeting uint `json:"meeting,omitempty" query:"meeting" jsonschema:"only materials of this zjazd (meeting) id"`
Tag string `json:"tag,omitempty" query:"tag" jsonschema:"only materials with this tag slug, at most 100 characters"`
Type string `json:"type,omitempty" query:"type" jsonschema:"only materials of this type, one of: pdf, notatka, link, inne"`
Page int `json:"page,omitempty" query:"page" jsonschema:"page number, 1 or more (default 1)"`
PerPage int `json:"per_page,omitempty" query:"per_page" jsonschema:"materials per page, 1 to 100"`
}
// materialInput is the input of bm_material.
type materialInput struct {
ID uint `json:"id" path:"id" jsonschema:"the material id, as listed by bm_materials"`
IncludeBody bool `json:"include_body,omitempty" query:"include_body" jsonschema:"true to include the material's body (it may hold transcript text)"`
}
// boolPtr returns a pointer to b, for the tool annotations.
func boolPtr(b bool) *bool { return &b }
// studentTools are the tools over the token twins of the student API. Each
// call runs as the session's user, who reaches only their own progress and
// published data, as on the HTTP API (D-09).
func studentTools() []pact.MCPTool {
return []pact.MCPTool{
{
Name: "dashboard",
Title: "BM dashboard",
Description: "Returns the student's dashboard: the next class sessions, the deadlines not yet due, the zjazd (meeting) under way today and the next one, and the student's reading progress counts." + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/dashboard",
Input: noInput{},
},
{
Name: "courses",
Title: "BM courses",
Description: "Lists the programme's courses with code, form, lecturer, semester, ECTS, assessment, syllabus link and counters." + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/courses",
Input: coursesInput{},
},
{
Name: "course",
Title: "BM course",
Description: "Returns one course with its description, syllabus link, counters, class sessions, readings (with the student's read and owned marks) and published materials." + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/courses/{id}",
Input: idInput{},
},
{
Name: "schedule",
Title: "BM schedule",
Description: "Lists the class sessions starting in a date range (default: from today, no end) with course, zjazd, start and end time, room, kind, lecturer, notes and whether the class is online." + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/schedule",
Input: rangeInput{},
},
{
Name: "meetings",
Title: "BM meetings",
Description: "Lists the zjazdy (weekend meetings) with number, first and last day, notes and the number of class sessions in each." + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/meetings",
Input: noInput{},
},
{
Name: "deadlines",
Title: "BM deadlines",
Description: "Lists the deadlines due in a date range (default: from today, no end) with title, due time, kind, course and description." + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/deadlines",
Input: rangeInput{},
},
{
Name: "readings",
Title: "BM readings",
Description: "Lists readings with author, source, link, course, zjazd, whether required, and the student's read and owned marks." + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/readings",
Input: readingsInput{},
},
{
Name: "mark_reading_read",
Title: "Mark a reading as read",
Description: "Marks a reading as read by the signed-in student and returns the reading id with the read time. Marking again keeps the first time." + needsScopeWrite,
Guard: "user",
Method: http.MethodPut,
Path: TokenPrefix + "/readings/{id}/progress",
Input: readingIDInput{},
},
{
Name: "unmark_reading_read",
Title: "Unmark a reading as read",
Description: "Removes the signed-in student's read mark from a reading (it can be marked again) and returns the reading id with an empty read time." + needsScopeWrite,
Guard: "user",
Method: http.MethodDelete,
Path: TokenPrefix + "/readings/{id}/progress",
Input: readingIDInput{},
Destructive: boolPtr(false),
Idempotent: boolPtr(true),
},
{
Name: "mark_reading_owned",
Title: "Mark a reading as owned",
Description: "Marks that the signed-in student has a copy of a reading and returns the reading id with the owned time." + needsScopeWrite,
Guard: "user",
Method: http.MethodPut,
Path: TokenPrefix + "/readings/{id}/owned",
Input: readingIDInput{},
},
{
Name: "unmark_reading_owned",
Title: "Unmark a reading as owned",
Description: "Removes the signed-in student's owned mark from a reading (it can be marked again) and returns the reading id with an empty owned time." + needsScopeWrite,
Guard: "user",
Method: http.MethodDelete,
Path: TokenPrefix + "/readings/{id}/owned",
Input: readingIDInput{},
Destructive: boolPtr(false),
Idempotent: boolPtr(true),
},
{
Name: "materials",
Title: "BM materials",
Description: "Lists a page of published materials, newest first, with title, type, course, zjazd, source, link, tags and file count, plus the page meta. " + MCPMaterialPrivacy + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/materials",
Input: materialsInput{},
},
{
Name: "material",
Title: "BM material",
Description: "Returns one published material with its tags and its files (id, absolute link, name, size and type). " + MCPMaterialPrivacy + needsScopeRead,
Guard: "user",
Method: http.MethodGet,
Path: TokenPrefix + "/materials/{id}",
Input: materialInput{},
},
}
}