feat(brain-mcp): read-only enumeration surface over HTTP (list/get/graph) — unblocks brain-audit's real read backend #71

Open
opened 2026-06-26 21:40:47 +00:00 by mathias · 1 comment
Owner

Why

mathias/brain-gardener Phase-1 brain-audit (issue #2) needs to read the entire brain corpus (every entry, every wing) to compute graph-health checks — orphans, broken [[wikilinks]], dup pairs, dangling supersede chains. The current brain-mcp surfaces can't enumerate that:

  • HTTP /query = BM25 search (top-N matches), not "list all".
  • brain_get = single path. MCP brain_query = search, same ceiling.

So there is no read path that returns the full set of entries. brain-audit's spine is blocked on it.

Decision context (from brain-gardener#2, comment 1770)

To not block the Phase-1 milestone, brain-audit will ship an interim filesystem reader over a read-only checkout of git.d-ma.be/mathias/brain (wiki/<wing>/<hall>/<slug>.md + knowledge/<slug>.md), behind a brain.Reader interface. That's strictly read-only, gives the full corpus + frontmatter, and satisfies both brain-audit invariants (no brain-write verb; no internet egress).

This issue is the proper long-term backend that drops in behind the same brain.Reader interface once it exists.

Proposed scope — strictly read-only

Add to the brain-mcp HTTP surface (ingestion/), authed like /query:

  1. /list (or brain_list) — enumerate all entry paths, optionally filtered by wing/hall. Paginated. Returns {path, content_hash, frontmatter} without bodies (cheap manifest).
  2. /get over HTTP — already exists as brain_get on MCP; expose the same read on HTTP so non-MCP Go consumers can fetch {path, frontmatter, body, content_hash}.
  3. /graph over HTTP — expose the existing brain_graph neighbors/subgraph ops so wikilink-graph checks don't have to reconstruct edges client-side.

Hard constraint: read-only. No write/update/tunnel verbs on this surface. brain-audit's invariant #1 (no brain-write path in the binary) means the consumer must not even be able to mutate through this API.

Open questions / feedback wanted

  • Is the interim FS reader acceptable as the Phase-1 backend, with this HTTP surface as a fast-follow — or do you want brain-audit to wait for this before going end-to-end? (My lean: ship FS reader now, this later; the brain.Reader seam makes the swap a one-file change.)
  • Does this belong in ingestion/ (the brain-mcp server) or is there a cleaner home given the #43 brain-interface analysis?
  • /list without bodies + lazy /get per node, or a single streaming full-dump endpoint? The audit reads everything, so a bulk dump might be simpler than N+1 gets — but breaks the per-node access pattern other consumers want.

Related: #43 (brain interface mismatch), #47 (read-after-write semantics — same read surface).

## Why `mathias/brain-gardener` Phase-1 `brain-audit` ([issue #2](https://git.d-ma.be/mathias/brain-gardener/issues/2)) needs to read the **entire brain corpus** (every entry, every wing) to compute graph-health checks — orphans, broken `[[wikilinks]]`, dup pairs, dangling supersede chains. The current brain-mcp surfaces can't enumerate that: - HTTP `/query` = BM25 **search** (top-N matches), not "list all". - `brain_get` = single path. MCP `brain_query` = search, same ceiling. So there is no read path that returns the full set of entries. brain-audit's spine is blocked on it. ## Decision context (from brain-gardener#2, comment 1770) To not block the Phase-1 milestone, brain-audit will ship an **interim filesystem reader** over a read-only checkout of `git.d-ma.be/mathias/brain` (`wiki/<wing>/<hall>/<slug>.md` + `knowledge/<slug>.md`), behind a `brain.Reader` interface. That's strictly read-only, gives the full corpus + frontmatter, and satisfies both brain-audit invariants (no brain-write verb; no internet egress). **This issue is the proper long-term backend** that drops in behind the same `brain.Reader` interface once it exists. ## Proposed scope — strictly read-only Add to the brain-mcp HTTP surface (`ingestion/`), authed like `/query`: 1. **`/list`** (or `brain_list`) — enumerate all entry paths, optionally filtered by wing/hall. Paginated. Returns `{path, content_hash, frontmatter}` without bodies (cheap manifest). 2. **`/get`** over HTTP — already exists as `brain_get` on MCP; expose the same read on HTTP so non-MCP Go consumers can fetch `{path, frontmatter, body, content_hash}`. 3. **`/graph`** over HTTP — expose the existing `brain_graph` neighbors/subgraph ops so wikilink-graph checks don't have to reconstruct edges client-side. **Hard constraint:** read-only. No write/update/tunnel verbs on this surface. brain-audit's invariant #1 (no brain-write path in the binary) means the consumer must not even be *able* to mutate through this API. ## Open questions / feedback wanted - Is the **interim FS reader acceptable** as the Phase-1 backend, with this HTTP surface as a fast-follow — or do you want brain-audit to wait for this before going end-to-end? (My lean: ship FS reader now, this later; the `brain.Reader` seam makes the swap a one-file change.) - Does this belong in `ingestion/` (the brain-mcp server) or is there a cleaner home given the #43 brain-interface analysis? - `/list` without bodies + lazy `/get` per node, or a single streaming full-dump endpoint? The audit reads everything, so a bulk dump might be simpler than N+1 gets — but breaks the per-node access pattern other consumers want. Related: #43 (brain interface mismatch), #47 (read-after-write semantics — same read surface).
Author
Owner

Approved scope. Ship FS reader now; this is the fast-follow.

Three answers:

1. Interim FS reader — yes, acceptable; do NOT wait on this issue.

The brain.Reader seam makes the swap a one-file change, so blocking the Phase-1 end-to-end milestone on a different repo's new endpoint would be self-inflicted. Ship the FS reader now (verified viable against mathias/brain HEAD — 674 md, correct wiki/<wing>/<hall>/ shape). This HTTP surface drops in behind the same interface later.

2. Home — ingestion/ is right, but sequence after #43.

It's the brain-mcp server, so that's the correct module. One dependency check: if #43 (brain-interface mismatch) is about to relocate or restructure that surface, build #71 after #43's structural call — don't bake new endpoints into a module that's about to move. Not a block, just a one-line check before you start.

3. Bulk-dump vs N+1 — do both; it's not actually a fork.

  • /list → bodiless manifest {path, content_hash, frontmatter}, paginated, wing/hall filter. Cheap.
  • bulk /get accepting a set of paths (or a streaming full-dump) → the audit's read-everything path.
  • single /get stays for per-node consumers.

The audit uses /list + bulk-fetch; other consumers use single /get. Bonus from the manifest's content_hash: the audit can skip-unchanged across runs later (incremental audits).

Hard constraint (non-negotiable)

The entire surface must be physically incapable of mutation — not merely "exposes no write," but no write/update/tunnel verb reachable through this auth scope at all. brain-audit's invariant #1 (no brain-write path in the binary) must hold even against a compromised or buggy consumer. If a write verb is reachable through the same credential, this surface fails its purpose.

No rush — FS reader is the unblock; this lands when #43 settles.

## Approved scope. Ship FS reader now; this is the fast-follow. Three answers: ### 1. Interim FS reader — **yes, acceptable; do NOT wait on this issue.** The `brain.Reader` seam makes the swap a one-file change, so blocking the Phase-1 end-to-end milestone on a different repo's new endpoint would be self-inflicted. Ship the FS reader now (verified viable against `mathias/brain` HEAD — 674 md, correct `wiki/<wing>/<hall>/` shape). This HTTP surface drops in behind the same interface later. ### 2. Home — **`ingestion/` is right, but sequence after #43.** It's the brain-mcp server, so that's the correct module. One dependency check: if #43 (brain-interface mismatch) is about to relocate or restructure that surface, build #71 *after* #43's structural call — don't bake new endpoints into a module that's about to move. Not a block, just a one-line check before you start. ### 3. Bulk-dump vs N+1 — **do both; it's not actually a fork.** - **`/list`** → bodiless manifest `{path, content_hash, frontmatter}`, paginated, wing/hall filter. Cheap. - **bulk `/get`** accepting a set of paths (or a streaming full-dump) → the audit's read-everything path. - **single `/get`** stays for per-node consumers. The audit uses `/list` + bulk-fetch; other consumers use single `/get`. Bonus from the manifest's `content_hash`: the audit can skip-unchanged across runs later (incremental audits). ### Hard constraint (non-negotiable) The entire surface must be **physically incapable of mutation** — not merely "exposes no write," but **no write/update/tunnel verb reachable through this auth scope at all**. brain-audit's invariant #1 (no brain-write path in the binary) must hold even against a compromised or buggy consumer. If a write verb is reachable through the same credential, this surface fails its purpose. No rush — FS reader is the unblock; this lands when #43 settles.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/hyperguild#71