Plan: MCP client ergonomics — cheaper turns, cheaper tokens
Status: drafted 2026-07-04 · Repo: demarkus (demarkus-mcp client) + demarkus-memory plugin · Decisions D1–D4 open (recommendations inline)
Problem
The main consumers of the soul and the knowledge system are MCP agents (Claude Code, Claude Desktop). For them the unit of cost is not the QUIC round-trip — it is the model turn per tool call and the context tokens per result. Today:
- Orientation costs a chain of turns: lookup → fetch → backlinks → fetch…
mark_fetchalways returns the whole body. Append-heavy status docs grow unbounded (the library roadmap is the live example — one fetch is a five- figure token spend), and agents usually need one section.- Agents re-fetch unchanged docs across a long session; every re-read pays full price.
- 14 tools per joined surface × several surfaces = schema budget + verb choice-paralysis.
Measured constraint (journal 2026-06-21): the link graph is hub-and-spoke (73% leaves) — computed graph queries (paths, centrality, communities) mostly re-derive the index tree, so the pre-made graph is NOT where client headroom is. Turns and tokens are.
Workstreams (priority order)
W1 — mark_fetch outline/section mode (token economy)
url#<heading-anchor>returns just that section (client slices the fetched markdown; no protocol change).- Outline mode returns the heading tree + opening paragraph + doc stats (version, modified, size, tags) with a "fetch url#section for detail" hint.
- Client caches the full body per session, so section follow-ups are free.
W2 — mark_explore composite (turn economy)
One tool call returns a neighborhood card for a URL: outline-mode head of the doc + backlinks (titles) + outbound links (titles) + sibling listing, each entry one line. Composes FETCH/LOOKUP/graph client-side. Caps per section (~10) with honest "+N more". Replaces the common 3–4-turn orientation chain.
W3 — session etag/version dedup
Re-fetch of an unchanged doc returns unchanged since v<N> (fetched earlier this session) instead of the body. Changed docs return the body plus a
version delta note. Per-process cache; a force param overrides.
W4 — polish (cheap, alongside)
- Tool descriptions teach the strategy: lookup-first, filters, explore for
orientation,
#sectionfetches, when NOT to fetch full bodies. - Plugin session-start injection stays the primary orientation channel (the index hubs ARE the map — Leiden finding); keep hub injection current.
Deferred / non-goals
- Client graph queries (path, common-neighbors, centrality): re-test when the knowledge system develops reciprocal cross-reference density (journal 2026-06-21's trigger). Not now.
- Server-side FTS / semantic search: real, but a server lane — separate plan when picked up.
- MCP resources + prompts (Desktop attach-without-turns): nice follow-up after W1–W3; needs demarkus-mcp resource support end-to-end.
- Lean tool profile (consumer vs authoring subsets): only if schema budget shows up as real pain; Claude Code already defers tool loading.
Decisions to settle
- D1 — outline default. Options: (a) opt-in param only; (b) outline by default for all; (c) size-adaptive: full body under ~8KB, outline above, always overridable (recommended — small docs keep today's behavior, monsters stop flooding context, no consumer breaks silently: the outline says how to get the rest).
- D2 — explore card contents. Recommended: outline head + backlinks + outbound + siblings, 10/section cap. Open: include lookup-by-shared-tags? (lean no for v1 — tag noise.)
- D3 — dedup scope. Recommended: per-process session, version+etag
keyed,
forceoverride; no cross-session persistence (staleness risk > saving). - D4 — where W1 slicing lives. Recommended: demarkus-mcp only (client
slices); the library's
openlibrarian tool adopts the same convention separately if wanted. Alternative: a shared client/markdown package in demarkus both consume.
Acceptance
A scripted orientation scenario ("what is project X, what phase, who decided") run before/after: tool turns and result tokens both drop materially (target: orientation ≤2 turns; no full-body fetch of an append-heavy status doc unless forced). Existing skills/hook flows (session-start, /promote, journal appends) unchanged.
Order
W1 → W3 (both inside mark_fetch's path, ship together) → W2 → W4 tool- description pass → then MCP resources/prompts as the follow-up phase.
SETTLED (2026-07-04, Fritz — all four on recommendation)
- D1: size-adaptive — full body under ~8KB, outline above, always overridable.
- D2: doc + backlinks + outbound + siblings, 10/section cap (no same-tag section in v1).
- D3: per-session dedup — version+etag keyed,
forceoverride, no cross-session persistence. - D4: shared package in demarkus (client-side markdown outline/section
slicing) — demarkus-mcp consumes now, the library's librarian
openadopts later.
Build order stands: W1 (+W3 together) → W2 → W4. Work lives in the
demarkus repo (demarkus-mcp + a shared client markdown package).
Pickup notes (for the fresh session in ~/latebit/demarkus)
- Where the code lives:
client/cmd/demarkus-mcp/main.go— a single ~1.3k-line file; all 14 tools registered in one block (s.AddTool(...)around line 61) with per-toolmarkXTool()definition +h.markXhandler pairs. W1/W3 modify themark_fetchdefinition + handler; W2 adds a new tool pair. Expect to split the file if it gets unwieldy — mind the repo's own conventions (read~/latebit/demarkus/CLAUDE.mdfirst). - Shared slicing package (D4): siblings live at
client/— fetch, graph, links, index, merge are separate packages there. New package (e.g.client/mdoutline) for heading-tree parse +#sectionslice + size gate; pure functions over markdown bytes, unit-test heavy (heading edge cases: setext headings, code fences containing#, duplicate headings → anchor dedup). - W3 dedup state: per-process map in the mcp handler struct keyed by
(host, path) → (version, etag); consult VERSIONS or the fetch response
metadata.
forceboolean param on mark_fetch busts it. - W1 outline shape: doc stats line (version/modified/tags/size) +
heading tree with per-section line counts + opening paragraph + hint line
(
fetch <url>#<anchor> for a section; force=true for the full body). 8KB threshold on the BODY size;#sectionandforcealways available regardless of size. - W2 explore composition: FETCH (outline head) + BACKLINKS + outbound
links (parse the fetched body's mark:// + relative links —
client/linksmay already do this) + LIST of the parent dir. 10/section, "+N more". - Anchor convention: match what the library's goldmark produces
(GitHub-style slugs) so
url#sectionmeans the same thing everywhere. - Gate: repo
pre-commit.sh. Never commit — Fritz commits. - Acceptance scenario: in a Claude Code session against the soul:
"what is the demarkus-library project and what phase is it in?" should
orient in ≤2 tool turns without a full-body fetch of
/demarkus-library/roadmap.md(the append-heavy monster that motivated W1).
BUILT (2026-07-04, branch feature/mcp-client-ergonomics — awaiting commit)
W1–W4 implemented and verified live against soul.demarkus.io; acceptance
met (orientation ≤2 turns, roadmap.md 51.9KB → 5.9KB outline, no forced
full-body fetch). New shared package client/mdoutline (D4); mark_fetch
gained #anchor + force + outline mode + session dedup; new
mark_explore tool (explore.go). Details in /journal/2026-07-04.md.
Follow-ups deferred: MCP resources/prompts, librarian open adoption,
broker gateway parity for the new fetch modes.
MERGED 2026-07-04 — PR #225 (8706768). W1–W4 + CodeRabbit round
(empty-identity dedup guard, etag-only change note) all on main. This plan
is complete except the deferred follow-ups listed above.
SHIPPED TO USERS 2026-07-05 — full release chain: #225 (client/v0.15.0) → #226 (provision pin, tools/v0.4.1) → #227 (bootstrap TOOLS_VERSION repin
- plugin bumps: memory 0.12.4, knowledge 0.5.4, pi-memory 0.12.5,
pi-knowledge 0.5.5). Plan closed; only the deferred follow-ups remain
(MCP resources/prompts, librarian
openadoption, broker gateway parity).
BROKER PARITY BUILT 2026-07-05 (was a deferred follow-up): the broker
MCP gateway now carries W1–W4 too — shared client/mdoutline/render.go
helpers, session-ID-keyed dedup (multi-tenant safe: no session → no
dedup; OnUnregisterSession eviction + caps), mark_explore as the 16th
tool, and the description pass. Branch feature/broker-ergonomics-parity,
awaiting commit. Remaining deferred: MCP resources/prompts, library
librarian open adoption.
BROKER PARITY MERGED 2026-07-05 — #230 (2d4dab7). Both MCP surfaces
now at W1–W4 parity; identity/notice logic shared via client/fetchdedup
(hoisted after review caught mirror drift twice). Reaches the live
knowledge system with the next broker release/deploy. Still deferred:
MCP resources/prompts, librarian open adoption.
LIBRARIAN ADOPTION MERGED 2026-07-05 (demarkus-library f08abfb): the
librarian's open tool now outlines ≥8KB docs and slices path#anchor
sections via client/mdoutline (dep bumped v0.12.40 → v0.17.0), replacing
its blind 16KB truncation. Every deferred follow-up of this plan is now
shipped — the last one standing anywhere is broker resources/prompts
(tracked in /plans/mcp-resources-prompts.md). This plan is fully closed.
Related documents
- MCP resources and prompts: the follow-up phase this plan names