md-graph client integration guide

How to use md-graph as a context layer for your application or agent. Self-sufficient: no source access needed.

What md-graph gives you

A hosted graph of markdown notes. Each node is a note (or a folder — same thing; folders are nodes with children). Nodes live in a tree via named edges; notes can also link to each other with typed, reasoned references; and every node can carry facets — namespaced JSON documents for structured app data that never touches the markdown.

The API in 5 minutes (no SDK needed)

md-graph speaks Connect protocol: plain HTTP POST + JSON.

POST <base-url>/mdgraph.v1.NodeService/<Method>
Authorization: Bearer <token>
Content-Type: application/json

Errors are JSON {"code": "...", "message": "..."} with matching HTTP status (not_found, permission_denied, already_exists, aborted = revision conflict, invalid_argument).

| Call | Body | Returns | |---|---|---| | NodeService/Resolve | {"path": "notes/2026-07-05.md"} | node (id, headRevisionId) | | NodeService/Get | {"nodeId": "..."} | node + body (base64) | | NodeService/ListChildren | {"nodeId": "..."} | children with names | | NodeService/Create | {"parentId": "...", "name": "x.md", "bodyKind": "BODY_KIND_MARKDOWN", "body": "<base64>"} | new node | | NodeService/PutRevision | {"nodeId": "...", "expectedHeadId": "...", "bodyKind": "BODY_KIND_MARKDOWN", "body": "<base64>"} | new revision | | EdgeService/Link | {"srcId": "...", "dstId": "...", "kind": "EDGE_KIND_REF", "rel": "relevant-to", "reason": "why"} | edge | | EdgeService/Refs | {"nodeId": "..."} | edges touching the node | | FacetService/GetFacet / SetFacet | {"subjectKind": "SUBJECT_KIND_NODE", "subjectId": "...", "namespace": "myapp.state", "data": "<base64 JSON>"} | facet |

body and facet data are base64 in JSON (proto bytes). Paths are always relative to your token's root — nothing outside it can even be named.

Worked example — read a note, update it safely:

H='-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json"'

# 1. resolve → node id + current head (for conflict-safe writes)
curl -s $BASE/mdgraph.v1.NodeService/Resolve $H -d '{"path":"context.md"}'
# → {"node":{"id":"…","headRevisionId":"…"}}

# 2. compare-and-swap write: expectedHeadId = the head you read
curl -s $BASE/mdgraph.v1.NodeService/PutRevision $H \
  -d '{"nodeId":"…","expectedHeadId":"…","bodyKind":"BODY_KIND_MARKDOWN","body":"'$(printf '# Context\n' | base64)'"}'
# → `aborted` means someone wrote between your read and write:
#   re-read, merge YOUR way, retry. The server never merges.

Agents can skip raw HTTP entirely: the mdg client's MCP mode (mdg mcp) exposes the same operations as tools with the token in env.

Best practices

  1. One grant per isolated context space (per group, per project). Isolation is free; use it. Store each token per space in your config.
  2. Always CAS. Read → carry headRevisionIdPutRevision with expectedHeadId → on aborted, re-read and retry (bounded).
  3. Facets: use your own namespace (myapp.*), whole-document replace, keep each document small (KBs).
  4. Markdown is for humans too. Write prose, use headings, use [[wikilinks]] — the server indexes them into a link graph.
  5. Asserted refs for judgments: Link with a rel and reason when your agent concludes two notes are related. These survive rewrites; wikilinks in prose don't.
  6. Names: no /, max 255 bytes, unique within a folder; end notes with .md.
  7. Binary data works (BODY_KIND_BINARY + mediaType) but keep originals in your own storage — this is a context layer, not a CDN.
  8. Deletes are tombstones; history survives. Pass recursive deliberately.
  9. Never log tokens; never put them in URLs. Header only.
  10. Uniform denials: a wrong token and an out-of-scope path look identical. If "everything vanished", suspect the token, not the data.

Failure modes

| Code | Meaning | Client behavior | |---|---|---| | aborted | CAS conflict | re-read, merge, retry ≤3 | | not_found | outside scope / gone / bad path | treat as absent | | already_exists | name collision | resolve + update instead | | unauthenticated | bad/revoked token | halt, surface to a human | | unavailable / 5xx | cold start or transient | backoff retry (1s, 5s, 25s) |

The service scales to zero; the first call after idle takes ~1–2s.


Copyright © 2026 Bejike Software, LLC