Files
sm-bm-plugin/controllers/api/mcp_view.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

86 lines
2.9 KiB
Go

package api
import (
"net/http"
"git.golem15.com/golem15/summercms/modules/bouncer"
)
// The MCP view of the material answers (D-17, D-21): a material's body may
// hold transcript text, so an answer to an MCP tool call leaves it out
// unless the call asks for it with include_body, and a write answer never
// echoes it. Plain HTTP answers are unchanged.
// viaMCP reports whether r is an MCP tool call: a request pier dispatched
// in-process with the api_token marker. The marker lives on the context
// only, so an HTTP request can never carry it.
func viaMCP(r *http.Request) bool {
_, ok := bouncer.Dispatched(r.Context(), "api_token")
return ok
}
// includeBody reports whether r asks for the material body
// (include_body=true or 1).
func includeBody(r *http.Request) bool {
return flag(r, "include_body")
}
// bodyOmitted reports whether leaving body out hides content.
func bodyOmitted(body *string) bool {
return body != nil && *body != ""
}
// mcpMaterialDetailJSON is a published material under MCP without its body:
// the list fields and the files, with body_omitted when a non-empty body was
// left out. Body shadows the embedded one and stays nil.
type mcpMaterialDetailJSON struct {
materialDetailJSON
Body *string `json:"body,omitempty"`
BodyOmitted bool `json:"body_omitted,omitempty"`
}
// mcpManagedMaterialJSON is a managed material under MCP without its body.
type mcpManagedMaterialJSON struct {
managedMaterialJSON
Body *string `json:"body,omitempty"`
BodyOmitted bool `json:"body_omitted,omitempty"`
}
// mcpManagedMaterialDetailJSON is a managed material with its files under
// MCP without its body.
type mcpManagedMaterialDetailJSON struct {
mcpManagedMaterialJSON
Files []fileJSON `json:"files"`
}
// materialView is the answer of GET /materials/{id} (student): unchanged
// over plain HTTP; under MCP (mcp) without the body unless withBody.
func materialView(d materialDetailJSON, mcp, withBody bool) any {
if !mcp || withBody {
return d
}
return mcpMaterialDetailJSON{materialDetailJSON: d, BodyOmitted: bodyOmitted(d.Body)}
}
// managedMaterialView is a managed material: unchanged over plain HTTP;
// under MCP (mcp) without the body unless withBody (a write answer passes
// false, so it never echoes the body).
func managedMaterialView(m managedMaterialJSON, mcp, withBody bool) any {
if !mcp || withBody {
return m
}
return mcpManagedMaterialJSON{managedMaterialJSON: m, BodyOmitted: bodyOmitted(m.Body)}
}
// managedMaterialDetailView is a managed material with its files, under the
// same rule as managedMaterialView.
func managedMaterialDetailView(d managedMaterialDetailJSON, mcp, withBody bool) any {
if !mcp || withBody {
return d
}
return mcpManagedMaterialDetailJSON{
mcpManagedMaterialJSON: mcpManagedMaterialJSON{managedMaterialJSON: d.managedMaterialJSON, BodyOmitted: bodyOmitted(d.Body)},
Files: d.Files,
}
}