package mcpsrv import ( "context" "fmt" "sourcecraft.dev/bigbes/sr-ht-spec/service" ) type readInput struct { Space string `json:"space" jsonschema:"the space to read from, written \"~owner/name\" as spec_list and spec_search report it"` Document string `json:"document" jsonschema:"which document: its frontmatter id (\"SPEC-0007\"), or its path with or without the \".md\" extension (\"specs/0007-storage\")"` Rev string `json:"rev,omitempty" jsonschema:"pin the read to one immutable revision, given as a git object name (lowercase hex, as returned in the rev field of any result). Omit this to read the space's approved head, which is what an agent almost always wants: the reviewed text. Branch names are not accepted."` } type readOutput struct { Space string `json:"space"` // ID is how this document is addressed: its frontmatter id when that is // well-formed and unique in the space, otherwise its path without the // extension. ID string `json:"id"` // DocID is the frontmatter id when the document has a well-formed one, and // empty otherwise. It differs from ID exactly when the document has no // usable id, which is the case an agent proposing an edit needs to see. DocID string `json:"doc_id,omitempty"` Path string `json:"path"` // Rev is the commit this content was read at. It is immutable: pass it back // as the rev argument to re-read these exact bytes. It is also the value // the Phase 3 write plane takes as If-Match when it is an approved head. Rev string `json:"rev"` // Blob is the sha of this document's content, and changes only when the // content does. Blob string `json:"blob"` // Pinned reports how Rev was chosen. False means the caller named no // revision and this is the space's approved head as of this call — the // reviewed, canonical text. True means the caller pinned a revision, and // whether that revision is on the approved branch is the caller's business: // this service does not claim it either way. Pinned bool `json:"pinned"` Title string `json:"title,omitempty"` Section string `json:"section,omitempty"` // Status is the document's authored lifecycle marker (draft, review, // superseded). It is not approval state: a document is approved by being // reachable from the approved ref, never by its frontmatter. Status string `json:"status,omitempty"` Summary string `json:"summary,omitempty"` Tags []string `json:"tags,omitempty"` // Markdown is the whole document, frontmatter included, exactly as stored. // It is the text to edit and send back when proposing a change. Markdown string `json:"markdown"` } func readHandler(ctx context.Context, b Backend, in readInput) (readOutput, error) { if err := requireRead(ctx); err != nil { return readOutput{}, err } ref, err := parseSpace(in.Space) if err != nil { return readOutput{}, err } rev, err := parseRev(in.Rev) if err != nil { return readOutput{}, err } sp, err := b.Docs.OpenSpace(ctx, ref) if err != nil { return readOutput{}, missingOrDenied(err, "spec_read", noSpace(ref)) } arc, bodies, resolved, err := archiveAt(ctx, b, sp, rev) if err != nil { return readOutput{}, missingOrDenied(err, "spec_read", noRevision(ref, rev)) } page, err := resolvePage(arc, in.Document) if err != nil { return readOutput{}, err } body, ok := bodies[page.Path] if !ok { // The archive is built from these very bodies, so a page without one // is a broken invariant rather than a missing document — this service's // fault and not an answer about the corpus, so it goes to the agent as a // protocol error and the detail goes to the log. Returning an empty // markdown field would be indistinguishable from an empty document. return readOutput{}, internalError( fmt.Errorf("document %s at %s in %s has no body", page.Path, resolved, ref), "spec_read") } return readOutput{ Space: ref.String(), ID: page.ID, DocID: page.DocID, Path: page.Path, Rev: resolved, Blob: page.Blob, Pinned: rev != service.ApprovedRev, Title: page.Title, Section: page.Section, Status: string(page.Status), Summary: page.Summary, Tags: page.Tags, Markdown: string(body), }, nil }