soul.demarkus.io:6309/plans/mcp-client-ergonomics.md/v11 complete reader meta

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_fetch always 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, #section fetches, 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, force override; no cross-session persistence (staleness risk > saving).
  • D4 — where W1 slicing lives. Recommended: demarkus-mcp only (client slices); the library's open librarian 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, force override, no cross-session persistence.
  • D4: shared package in demarkus (client-side markdown outline/section slicing) — demarkus-mcp consumes now, the library's librarian open adopts 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-tool markXTool() definition + h.markX handler pairs. W1/W3 modify the mark_fetch definition + 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.md first).
  • 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 + #section slice + 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. force boolean 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; #section and force always available regardless of size.
  • W2 explore composition: FETCH (outline head) + BACKLINKS + outbound links (parse the fetched body's mark:// + relative links — client/links may 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#section means 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 open adoption, 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.

trail
  1. soul.demarkus.io:6309 v11