Plan: Broker MCP Gateway (Knowledge-System Layer)
Plan rewritten 2026-05-20. v1 described a REST HTTP API. After alignment with Fritz, the architectural target shifted: the gateway is an MCP server exposed at a single
/mcpendpoint, authenticated against the company SSO (id_token bearer), with world access-tokens cached broker-side per session. Agent-as-client is the only first-pass consumer. A browser/REST surface is a later, additive plan if a real consumer asks.v3 (2026-05-20 same session): Open Question 1 resolved (
mark://{worldName}/{path}with cluster-internal DNS resolution). PR6 (tools/demarkus-joinbinary) canceled outright; the join flow becomes the/knowledge-joinslash command inplugins/claude-code/, distinct from a future/soul-join(personal direct-QUIC). Vocabulary pin: knowledge system = organizational brokered universe; soul = personal, direct-QUIC. Different commands, different deployment shapes, shared protocol underneath.
The enterprise-deployment piece. Adds a remote MCP server to the broker that exposes the demarkus tool surface (mark_fetch, mark_publish, etc.) to plugin-style agents over the company's existing SSO. Worlds stay cluster-internal; the broker becomes the single MCP endpoint the plugin connects to; corporate networks that block QUIC over UDP reach the universe over standard HTTPS. The plugin's local vault holds only the id_token (the company SSO bearer); world access-tokens are minted lazily broker-side per session, scoped to the authenticated user, never persisted to disk.
Architectural framing in /thoughts.md § "On the Protocol as the Permanent Layer": demarkus core is the permanent layer; this gateway is a knowledge-system overlay that the protocol doesn't know exists. See also feedback_core_vs_knowledge_layer.md in agent auto-memory.
Why this is the priority
Stated 2026-05-20: "this has to be the next task we do, it is the missing piece that the company needs." Two enterprise blockers dissolve together:
- "20 dev teams × N worlds plugin-config grind." Without the gateway, every developer at a 20-team org installs N local MCP servers per workstation, one per world they need. With the gateway, every developer adds ONE MCP server entry (the broker URL) and the broker exposes the universe's worlds as a single tool surface.
- Corporate-network UDP blocking. QUIC over UDP gets blocked by enterprise firewalls, transparent proxies, and DPI appliances. HTTPS over 443 works everywhere. The gateway speaks HTTPS publicly and translates to QUIC internally.
Universe-onboarding's PR6/7/8 are reshaped by this work: PR6 (tools/demarkus-join binary) is canceled. The join flow folds into /knowledge-join in plugins/claude-code/. PR7 becomes "plugin slash command implementation" and PR8 is documentation.
Vocabulary
| Term | Meaning |
|---|---|
| knowledge system | An organizational, broker-fronted universe. Joined via /knowledge-join. MCP traffic terminates at the broker over HTTPS; broker translates to QUIC and routes to internal worlds. The deployment shape this plan ships. |
| soul | A personal demarkus knowledge base — direct-QUIC, no broker required. Joined via a future /soul-join if needed. The original demarkus deployment shape. |
| world | A demarkus server (QUIC). Both knowledge systems and souls compose one or more worlds. |
| universe | An operator-managed bundle of worlds + (for knowledge systems) a broker. |
A single Claude Code installation can have BOTH a knowledge system (the org's) and one or more souls (personal). They don't conflict — different commands, different MCP server entries, different auth modes.
Non-Negotiables
- No protocol changes. The demarkus message format (FETCH/PUBLISH/APPEND/VERSIONS/LIST/ARCHIVE/MERGE/etc.), content addressing, versioning, and capability-token mechanics are unchanged.
- No demarkus-server changes. Worlds keep their current QUIC +
markALPN listener. They see a bearer token in the demarkus protocol header (the world access-token the broker is holding on the user's behalf) and serve it. They do not know the broker is the caller. - Capabilities stay on worlds. The world is the trust boundary for what a token can do. The broker holds world access-tokens in trust per-authenticated-user; the world validates them on every request. The broker does NOT re-implement capability checks.
- Tool surface parity with the local MCP server. All 13 tools the existing
client/cmd/demarkus-mcpexposes (mark_fetch, mark_list, mark_versions, mark_publish, mark_append, mark_archive, mark_discover, mark_resolve, mark_index, mark_backlinks, mark_graph, mark_graph_export, mark_graph_publish) are exposed by the broker MCP from day one. Anything else is a plugin-side change, not a gateway concern. - Opt-in deployment. The MCP listener is off by default. Operators enable it explicitly; the existing direct-QUIC universe deployment stays the default.
- No MVPs, no shortcuts. OAuth correctness, per-session world-token cache lifecycle (lazy mint + eviction on id_token expiry + cap on token churn), structured error envelopes, content-hash propagation through the tool surface, conflict-aware merge — all from day one within each slice (not later).
- QUIC stays first-class for direct-world access. This gateway is an enterprise overlay, not a transport replacement. The CLI, Obsidian plugin, and in-cluster server-to-server traffic all keep direct-QUIC access. The broker's existing /auth, /me/install, /tokens surfaces all stay as they are.
Out of Scope (explicit)
- Browsable REST surface. Deferred until a real non-agent consumer asks. The plugin is the only first-pass consumer; the agent is always the client.
tools/demarkus-joinbinary. Canceled (see v3 changelog above). The join flow is a slash command, not a binary.- mTLS broker → world. Confirmed deferred to a hardening pass. TLS terminates at the broker; broker→world QUIC traffic stays inside the cluster, bounded by NetworkPolicy + RBAC.
- Persistent world-token storage. Raw world tokens are NEVER persisted to disk by the broker. They live in process memory per session; broker restart drops them; next tool call re-mints. This matches the broker's existing posture (raw tokens are never recoverable from the issuances Secret).
- MCP
resourcesandpromptsfeatures. Only thetoolscapability is exposed. Resources (file-style refs) and prompts (templates) are not part of the demarkus model. - Long-lived subscriptions / server-initiated events. Polling is the model (
/thoughts.md§ "On Subscriptions and Polling"). - Multi-broker / multi-universe. Single broker per universe stays the model.
- Content negotiation beyond
text/markdown. Same scope contract as the protocol (project_scope_markdown_only.md).
Architecture
┌──────────────┐ HTTPS (MCP) ┌──────────────────────────────────┐ QUIC ┌─────────┐
│ Claude Code │ ─────────────► │ broker (/mcp endpoint) │ ─────► │ world A │
│ plugin │ │ │ │ (cluster│
│ (or any MCP │ ◄───────────── │ JSON-RPC over Streamable HTTP │ ◄───── │ internal)
│ client) │ tool result │ │ └─────────┘
└──────────────┘ │ Authorization: Bearer <id_token> │
│ - requireAuth (PR4 compositeV) │ ┌─────────┐
│ - per-subject rate limit │ ─────► │ world B │
│ │ ◄───── │ │
│ Per-session world-token cache: │ └─────────┘
│ - key: (subject_hash, world_name) │
│ - mint lazily via Issuer.Mint- │
│ Filtered on cache miss │
│ - evict on id_token / token expiry│
│ │
│ Tool URLs: mark://{worldName}/ │
│ {path} — broker resolves worldName│
│ to <ns>.svc.cluster.local:6309 │
│ via cfg.Worlds[].Namespace │
│ │
│ 13 tools (parity with local │
│ demarkus-mcp): mark_fetch, ... │
│ │
│ OAuth metadata: │
│ /.well-known/oauth-protected- │
│ resource (RFC 9728) │
│ /.well-known/oauth-authorization- │
│ server (RFC 8414) │
└──────────────────────────────────┘
Layer responsibilities
| Component | Owns | Does NOT own |
|---|---|---|
mcpGateway HTTP listener |
Listener / TLS / single /mcp endpoint dispatch. Streamable HTTP transport per current MCP spec. Separate from existing management API listener. |
Tool semantics, auth, session state. |
| MCP protocol layer | Handling initialize, tools/list, tools/call JSON-RPC methods. Capability negotiation. MCP error envelope. |
demarkus protocol semantics. |
gatewayAuth middleware |
Validating the id_token bearer via existing PR4 compositeVerifier (broker-signed OR IdP-signed). Subject extraction for session keying. Per-subject rate limiting. |
World-level capability enforcement. |
sessionCache |
Per-session in-memory map keyed by subject_hash. Holds cached world access-tokens (raw, never persisted). Lazy-mint via Issuer.MintFiltered. Eviction on id_token expiry, world-token expiry, or LRU cap. |
Auth, tool dispatch. |
| Per-tool handlers | One per MCP tool. Parses tool args (URL form: mark://{worldName}/{path}), resolves the addressed world via cfg.Worlds by name, fetches/mints the world token from sessionCache, dispatches via fetch.Client / merge.Candidate. Surfaces errors via MCP error envelope. |
Transport, connection pool, session state. |
worldPool |
Per-world fetch.Client reuse keyed by worldName. Resolves the internal address from cfg.Worlds[].Namespace (e.g. <name>.<namespace>.svc.cluster.local:6309). Connection lifecycle. Reconnect on transport failure. Bounded parallelism per world. |
Auth, tool semantics. |
cfg.Server.MCP |
MCP-gateway-specific config: Enabled, Addr, TLS{...}, SessionMaxIdle, WorldTokenTTL, WorldPool{...}. Validated at LoadConfig. |
Wire format. |
World name → internal address resolution
Tool URLs have the form mark://{worldName}/{path}. The broker resolves worldName to a cluster-internal address using the existing cfg.Worlds[].Namespace field. Default resolution:
mark://team-a/foo.md → mark://team-a.team-a.svc.cluster.local:6309/foo.md
^^^^^^ ^^^^^^
name namespace (from cfg.Worlds[].Namespace)
This is the standard Kubernetes Service DNS pattern. The world's Service must be named the same as the world (default for the demarkus-server chart). If an operator deploys a world with a non-default Service name, they configure cfg.Worlds[].InternalAddress explicitly — overrides the default DNS pattern. Default empty means "use the convention."
Pre-Flight Tasks (one PR ahead of Slice 1)
Pre-Flight 0 — Hoist client/internal/fetch to public
client/internal/fetch/fetch.go→client/fetch/fetch.go(orclient/pkg/fetch/fetch.go). Mirror forclient/internal/mergesince MERGE support lands in MVP via the conflict-awaremark_publishtool.- Update import paths in
client/cmd/demarkus-mcp,client/cmd/demarkus,client/cmd/demarkus-tui(and any other consumers within the client module). - Same content; just relocated. Surface unchanged.
- Why: Go's internal-package rule blocks any module outside
client/from importingclient/internal/.... The broker is intools/demarkus-broker(different module under monorepo replace-directives), so it can't import the existing client library while it's internal. - This is a CLIENT-module API change, not a protocol-core change. Not gated by
feedback_plugin_scope.md(broker isn't a plugin), but worth flagging explicitly so reviewers see the architectural shift.
Pre-Flight 1 — Verify mark3labs/mcp-go's Streamable HTTP support
- The existing
client/cmd/demarkus-mcpuses mcp-go's stdio transport (mcpserver.ServeStdio()). Confirm mcp-go v0.44+ supports Streamable HTTP (the current MCP spec's preferred remote transport). - If yes: lifecycle the new gateway around mcp-go's HTTP server constructor.
- If no: upgrade to a version that does, swap to a different MCP Go library, or implement Streamable HTTP transport in-house (worst case).
- Output: one-paragraph journal note on transport choice before Slice 1 begins.
Sub-Tasks (sequenced; one slice per PR)
Slice 1 — Foundation: listener + initialize + tools/list (no tool implementations)
tools/demarkus-broker/internal/broker/mcp_gateway.go(new) —mcpGatewaylistener, JSON-RPC dispatcher, Streamable HTTP transport. Separate listener from the existing management API.tools/demarkus-broker/internal/broker/mcp_oauth.go(new) — OAuth metadata endpoints:/.well-known/oauth-protected-resource(RFC 9728),/.well-known/oauth-authorization-server(RFC 8414). Reuses broker's existingDiscoverymachinery.tools/demarkus-broker/internal/broker/mcp_auth.go(new) —gatewayAuthmiddleware. id_token bearer extraction, validation via existingcompositeVerifier, subject claim extraction, per-subject rate limiting (shared budget with /tokens routes — same identity, one bucket).tools/demarkus-broker/internal/broker/config.go—ServerConfig.MCPsubstruct:Enabled,Addr,TLS{...},SessionMaxIdle,WorldTokenTTL. PlusWorldConfig.InternalAddress(optional override for the default Service-DNS resolution).tools/demarkus-broker/main.go— start the MCP listener when enabled, alongside the management API.tools/demarkus-broker/internal/broker/mcp_initialize.go(new) — handles MCPinitializeJSON-RPC method. Advertises server capabilities (tools only).tools/demarkus-broker/internal/broker/mcp_tools_list.go(new) — handlestools/list. Returns the 13 tool definitions (names + JSON schemas) without implementations yet. Definitions mirrorclient/cmd/demarkus-mcpexactly.- Tests: OAuth metadata round-trip, initialize handshake, tools/list returns the 13 expected tool names, unauth requests rejected with 401 + WWW-Authenticate header pointing at auth-server metadata URL, expired/revoked bearer rejected, per-subject rate limit triggers 429.
Slice 2 — Read tools + session cache foundation: mark_fetch, mark_list, mark_versions
tools/demarkus-broker/internal/broker/mcp_session.go(new) —sessionCachekeyed bysubject_hash. Holdsmap[worldName]cachedWorldToken{raw, expiresAt}. Lazy initialization on first cache miss. LRU cap (default 10000 subjects) + idle eviction (SessionMaxIdledefault 1h after last use).tools/demarkus-broker/internal/broker/world_pool.go(new) —worldPoolforfetch.Clientreuse per world. Resolves worldName → internal address via the rule documented above.tools/demarkus-broker/internal/broker/mcp_tools_read.go(new) — handlers formark_fetch,mark_list,mark_versions. URL parameter shape:mark://{worldName}/{path}. Resolves world by name; lazy-mints world access-token viaIssuer.MintFilteredon cache miss; callsfetch.Client.Fetch/List/Versionswith the token; maps the demarkus Result to an MCP tool response (matching local MCP server output format for parity).- Tests: each tool happy path, world-not-found tool error, document-not-found maps to MCP error, expired-bearer 401 mid-call, cache hit reuses token, cache miss mints, two concurrent calls for the same (subject, world) coalesce to one mint (singleflight), session eviction on id_token expiry causes re-mint.
Slice 3 — Write tools: mark_publish, mark_append, mark_archive
tools/demarkus-broker/internal/broker/mcp_tools_write.go(new) — handlers for the three write ops.mark_publishreuses the existingexpected_version+on_conflictshape from the local MCP server. Conflict-aware merge candidate flow lands in Slice 6; for Slice 3, on-conflict defaults to "fail" with the conflict envelope, NOT the merge-candidate path.mark_appendreuses the auto-resolved-version pattern (omitexpected_version, broker calls VERSIONS internally) — matches local MCP server behavior.mark_archivedeletes by archiving (per the demarkus protocol's actual DELETE shape; the protocol op is ARCHIVE, not DELETE).- Tests: happy paths, version-mismatch conflict, missing-expected_version on PUBLISH 400, auto-resolve path for APPEND, world-side RBAC failure surfaces as MCP tool error.
Slice 4 — Federation read tools: mark_discover, mark_resolve, mark_backlinks, mark_graph
tools/demarkus-broker/internal/broker/mcp_tools_federation.go(new) — handlers that delegate to whichever demarkus client library functions back the local MCP server's federation tools (mapped during a second-pass spike at start of Slice 4, deferred until Pre-Flight 0 hoist completes and the public API is visible).- These tools are read-only and don't change the session-cache shape.
- Tests: happy paths + edge cases per tool.
Slice 5 — Federation write tools: mark_index, mark_graph_export, mark_graph_publish
tools/demarkus-broker/internal/broker/mcp_tools_federation_write.go(new).- Same shape as Slice 3 for writes (expected_version, conflict handling).
- Tests: happy paths + conflict cases.
Slice 6 — Conflict-aware merge in mark_publish
- Reuses
client/internal/merge(hoisted toclient/mergein Pre-Flight 0). - Adds the
on_conflict: "merge"branch tomark_publish, matching local MCP server behavior. - Tests: clean merge, structural merge, conflict-markers returned.
Slice 7 — Chart, RBAC, docs
deploy/helm/demarkus-broker/values.yaml—server.mcp.enabled,server.mcp.addr,server.mcp.tls(existingSecretRefrecommended),server.mcp.sessionMaxIdle,server.mcp.worldTokenTTL,server.mcp.worldPool. Plusworlds[].internalAddressfor non-default Service DNS overrides.- Templates: deployment.yaml (new containerPort + TLS mount), service.yaml (gateway port exposed), ingress.yaml (route mcp host to new port), networkpolicy.yaml (allow ingress on mcp port).
- No new RBAC needed — broker SA already has perms for issuances Secret + world Secrets that lazy-mint touches.
deploy/helm/demarkus-broker/README.md— new "MCP gateway" section. When to enable, TLS setup, the plugin-side flow (/knowledge-joinfrom Claude Code), OAuth flow, rate-limit behavior.tools/demarkus-broker/main.gopackage doc — bump Current scope to mention/mcp.- New
tools/demarkus-broker/MCP-API.md— operator/developer-facing spec for the MCP tool surface mirrored fromclient/cmd/demarkus-mcp. Lives in the broker package; not a protocol document.
Slice 8 — Plugin /knowledge-join slash command (replaces canceled PR6)
plugins/claude-code/commands/knowledge-join.md(new, per Claude Code plugin slash-command shape) — prompt-shaped command that takes a broker URL argument.- Behavior:
- Validates the broker URL by issuing
HEAD <url>/.well-known/oauth-protected-resource. - Derives a slug from the broker hostname (e.g.,
acmefrombroker.acme.com). - Runs
claude mcp add --transport http {slug} {url}/mcp. Claude Code handles the OAuth device flow when the plugin first talks to the MCP server. - Reports success + slug to the user; on failure shows the validation error.
- Validates the broker URL by issuing
plugins/claude-code/scripts/lib.sh— bump SERVER/CLIENT/TOOLS_VERSION pins perfeedback_plugin_version_pins.md.- Tests: shell-script tests in
plugins/claude-code/tests/covering happy path (mock broker URL), validation-fail path, slug-derivation cases.
Scope estimate
| Slice | Production code | Tests | Chart / docs |
|---|---|---|---|
| Pre-Flight 0 (hoist) | ~50 (file moves + import updates) | — | — |
| Pre-Flight 1 (mcp-go spike) | 0 | 0 | — |
| 1. Foundation + initialize + tools/list | ~500 | ~600 | — |
| 2. Read tools + session cache | ~400 | ~500 | — |
| 3. Write tools | ~250 | ~400 | — |
| 4. Federation read tools | ~200 | ~300 | — |
| 5. Federation write tools | ~150 | ~250 | — |
| 6. Conflict-aware merge | ~150 | ~250 | — |
| 7. Chart + RBAC + docs | ~50 | ~80 (helm-unittest) | ~300 |
8. Plugin /knowledge-join |
~50 | ~80 | — |
| Total | ~1800 | ~2460 | ~300 |
Eight slices (plus Pre-Flight) across ~2-3 working weeks. Slice 8 is small but ships separately so the broker side can land + bake before plugin work touches it.
Open Questions To Resolve Before/During Implementation
URL shape inside MCP tool args.Resolved 2026-05-20:mark://{worldName}/{path}— broker resolves worldName to cluster-internal Service DNS (<name>.<namespace>.svc.cluster.local:6309);worlds[].internalAddressoverrides the default for non-conforming deployments.- MCP transport: Streamable HTTP vs SSE. Lean: Streamable HTTP per current MCP spec. Locked in by Pre-Flight 1.
- OAuth authorization-server identity. The broker is BOTH the MCP server (resource) AND the authorization server (the existing OIDC + device-flow surface). Lean: broker advertises itself as the authorization server via
/.well-known/oauth-authorization-server. The actual IdP (Google, Okta, etc.) is one hop further — handled by existing broker OIDC machinery. Matches PR3 device-flow architecture. - Session keying. Lean: subject claim hash from id_token. Multiple devices for the same user share one session.
- World-token TTL and minting cadence. Lean: cache world-tokens with their natural expiry from
MintFiltered. On id_token rotation (refresh-grant), session_cache survives because subject claim is stable across refreshes. - Federation tool implementations. Slice 4/5 reuse the client library functions backing the existing federation tools. Second-pass spike at start of Slice 4.
- MCP error envelope for partial mint failures. When the broker mints a world token on first call, partial-mint surfaces as ONE successful tool response + server log entry, NOT a partial-mint envelope.
- MCP session lifecycle on broker restart. Lean: in-memory only; broker restart drops all sessions; plugin re-authenticates via the existing OAuth refresh path (PR4 broker-signed refresh tokens). Persistent session storage deferred.
Risks Specific To The MCP Gateway
- MCP protocol surface gains an internet-reachable port. Same risk shape as the original REST plan: hardened auth middleware (existing compositeVerifier), strict per-subject rate limit, structured MCP error envelope, TLS-only in production.
- World-token cache memory growth. A broker serving 10k subjects × 5 worlds × ~200 bytes per cached token entry = ~10MB. Bounded but not zero. Mitigation: LRU cap, idle eviction (default 1h after last tool call), per-process memory metric for ops dashboards.
- Mint storm on broker restart. Every active plugin reconnects after a restart and re-mints tokens for every world it touches. Mitigation:
mutateSecret's optimistic-concurrency retry handles contention; sharded issuances Secret if a real customer hits the wall (existing Phase 7 path). - Issuance bloat under high session churn. Plugin sessions that come and go faster than the world-token TTL leak issuance records into the Secret. Sweeper retires them on expiry.
- Plugin holds N world identities through one broker connection. Single MCP-server-for-the-universe architecture means a buggy broker handler could expose one user's session to another's bearer. Mitigation: strict subject-claim keying on session cache; every tool call re-validates the bearer against the cache key; integration test that proves cross-subject isolation under concurrent load.
- mcp-go library maturity. Third-party Go MCP library; if it lacks Streamable HTTP, the choice in Pre-Flight 1 cascades. Mitigation: Pre-Flight 1 is fact-finding; the next decision lands as a one-paragraph journal entry before Slice 1.
- OAuth metadata vs OIDC metadata overlap. Broker already serves
/.well-known/openid-configuration. RFC 8414 (OAuth) and OIDC discovery have largely-overlapping shapes; serving both means two endpoints that mostly mirror each other. Mitigation: one shared rendering helper, two route registrations. - Plugin slash-command surface name collision.
/knowledge-joinis new; verify no other plugin in the marketplace uses it. Mitigation: the slash command is namespaced to the demarkus-memory plugin; slot the command into the plugin'scommands/directory and Claude Code's resolution handles namespacing.
Touch Points With Adjacent Work
- Universe-onboarding PR5 (shipped #141, 2026-05-20). No code coupling.
/me/installstays as-is — identity-introspection surface alongside the MCP gateway's operational data plane. - Universe-onboarding PR6 (
tools/demarkus-join): canceled. Replaced by Slice 8 (/knowledge-joinplugin slash command). - Universe-onboarding PR7/PR8: substantially reshaped. PR7 (originally "plugin slash commands") may merge into Slice 8; PR8 (originally docs) merges into Slice 7 + Slice 8's docs.
- Future
/soul-joinplugin slash command (separate plan): direct-QUIC join for personal souls. Distinct from/knowledge-join(organizational, broker-fronted). Same vocabulary axis as the project's owndemarkus-soulvs the eventual enterprise knowledge systems. /thoughts.md§ "On the Protocol as the Permanent Layer": principle this plan is built on.- Future browsable REST surface plan: additive. The MCP gateway is on
/mcp; a REST surface would live on/v1/...paths on the same listener, sharing OAuth auth and session cache. - Future mTLS broker → world plan: anchors here. The worldPool can grow mTLS in place without changing the tool surface.
Resume Steps (when starting work)
- PR5 (#141) confirmed on main (
6c1c354). mark_fetch /plans/broker-https-gateway.md(this doc).- Open Questions: only #2-#8 remain (lean answers documented).
- Execute Pre-Flight 0 (hoist) as its own small PR. Existing tests pass after the relocation.
- Execute Pre-Flight 1 (mcp-go transport spike). Output: one-paragraph note in the journal.
- Cut branch
feat-tools-broker-mcp-gateway-foundation. Start at Slice 1. - Each slice is its own PR.
go test -race+bash pre-commit.shgreen before moving on. - Journal at session end. Update Implementation Status (below) as slices land.
Done When
- All eight slices merged (plus Pre-Flight 0).
go test -race ./...green insidetools/demarkus-broker/andclient/.helm unittest .green for the broker chart.pre-commit.shgreen.- Manual end-to-end: user runs
/knowledge-join https://broker.example.comin Claude Code → OAuth device flow completes → plugin sees all 13 demarkus tools →mark_fetch mark://team-a/foo.mdreturns the world's content; cached world-token reused on the next call; broker logs show only one mint per (subject, world). - Operator-facing README documents the deployment.
- Journal entry covering any design decisions that landed differently from this plan.
Implementation Status
Not started. Plan v3 published 2026-05-20:
- v1: REST HTTP API
- v2: pivoted to MCP-server architecture
- v3: URL shape resolved (
mark://{worldName}/{path}), PR6 canceled,/knowledge-joinplugin slash command becomes Slice 8
Awaiting kick-off. Pre-Flight 0 (hoist client/internal/fetch) begins next, as its own PR.