Skip to content

Open Knowledge Format (OKF) with markdown-vault-mcp

Open Knowledge Format (OKF) is a vendor-neutral convention for a folder of markdown files that serves as curated context for AI agents. A bundle is a directory of notes with YAML frontmatter: one concept per file, the file path as the concept identity, and a small set of frontmatter fields that describe each note's type, lifecycle, provenance, and trust. This guide shows how markdown-vault-mcp recognises OKF bundles, what it does with the metadata, and how to move an existing vault into the format.

Note

OKF support is read-only by default. Recognising a bundle only ever adds annotations and advice; it never changes your files. The migration tools that do change files are explicit, and this guide covers them near the end.

Example pack

The examples/okf/ directory ships a declaration index, typed note templates (Concept, Capture), and a prompt pack (author a concept, verify a note, triage stale content, migrate a vault) that you can copy into your vault and prompt folders.

How the server recognises a bundle

The server treats a vault as an OKF bundle when the root index.md declares a version in its frontmatter:

---
okf_version: "0.2"
---
# My bundle

This declaration travels with the vault, so the same bundle behaves the same way here and in other OKF tools. The MARKDOWN_VAULT_MCP_OKF_MODE environment variable controls how the server responds to it:

Value Behaviour
auto (default) Apply OKF read semantics when the root index.md declares okf_version.
off Never apply OKF semantics, and hide the OKF tools. Use this if your frontmatter happens to use keys such as status for an unrelated purpose.
on Force OKF semantics even without a declaration. Use this for a bundle you consume but do not own.

Check the current state at any time through the okf section of the stats tool, or the config://vault resource.

What the metadata means

On a recognised bundle, the server reads a few OKF frontmatter families and surfaces them in search, read, and get_context results under an okf key:

  • Type: the free-text type field, such as Playbook, Metric, or Reference.
  • Lifecycle: status is one of draft, stable, or deprecated. A note with no status is treated as stable. stale_after marks a note stale from a date on (2026-12-31) or from an instant on (2026-12-31T00:00:00Z; the offset is required).
  • Trust tier: derived from the verified list (a single entry may be written as a bare mapping, per the spec). A note verified by a person (by: human:...) is human-reviewed; a note verified only by a process is machine-confirmed; an unverified note is unverified.
  • Sources: the sources provenance list, surfaced in full on read and as a count on search hits.

You can filter on these dimensions. search and list_documents accept status, stale, trust_tier, and type filters, so {"stale": "true"} or {"status": "deprecated"} builds a triage listing. See the tools reference for the full filter set.

Frontmatter fields

A conformant note needs only a non-empty type. Everything else is optional but recommended:

---
type: Playbook
title: Onboarding a new teammate
description: Step-by-step setup for the first week.
tags: [process, people]
status: stable
stale_after: 2027-01-01
sources:
  - resource: https://example.com/handbook
    id: handbook
verified:
  - by: human:alex
    at: "2026-08-01T09:14:00Z"
---

Migrating an existing vault

Adoption is incremental. A note is either conformant or not, and the server tolerates a mix, so you can convert a vault gradually rather than in one step. The recommended order is a ratchet: declare early so new notes are written correctly, then converge the backlog.

1. Audit

Run the okf_validate tool to see where the vault stands. It reports conformance as a degree rather than a pass or fail. The report gives the conformant count out of the total, alongside a per-rule breakdown with example paths (notes missing a type, unknown status values, and the like). The audit reads from disk, so it works before you declare anything. Use it to decide whether to start.

The report also lists index.md files that carry frontmatter the format does not allow. That includes index files the server generated: with MARKDOWN_VAULT_MCP_REQUIRED_FIELDS set, the server writes the required fields into them so they stay indexed. Leave those fields in place, because an index without them drops out of search and document listings.

2. Declare

Add okf_version: "0.2" to the root index.md frontmatter. From this point the server annotates results, the filters work, and the agent receives guidance to keep OKF conventions when it edits. Declaring early means every new note is authored conformantly while you work through the backlog.

3. Enrich

Backfill the missing metadata. This is where most of the work is, and an agent can do it note by note. For each note it proposes a type from the content and fills in the title and description. Approve the changes in batches, then re-run okf_validate to watch the conformant count climb.

When using write to update an existing note or root index, read its current content first and pass that read's etag as if_match. New files omit if_match. A failed replacement leaves the existing file intact; report the conflict before continuing with any dependent migration steps.

4. Mechanical transforms

Three tools handle the changes you should not make by hand. They are write tools, so they are hidden in read-only mode and when OKF_MODE is off, and on a git-backed vault each change is committed.

  • okf_convert_links rewrites [[wikilinks]] as the root-absolute markdown links OKF recommends, such as [text](/guides/note.md). Only links whose target exists are converted, so your link graph is preserved exactly. Unresolvable wikilinks are left alone and reported. You can write in either link style day to day; run this before sharing the bundle.
  • okf_generate_index writes a folder's index.md as a listing of the notes directly in that folder, plus a pointer into each subfolder's own index.md. It draws the description from each note's frontmatter and lists one level at a time, so a nested bundle stays navigable rather than flattening into one long page. It preserves existing frontmatter, so regenerating the root index.md keeps your okf_version declaration.
  • okf_seed_log creates a log.md change history from the vault's git commits, newest first. The folder argument both places the log and scopes its content: seeding a folder includes only the commits that touched that subtree, while seeding the bundle root includes the whole vault's history. It refuses to overwrite an existing log.md, so a hand-maintained history is safe.

Reserved files on a vault with required fields

If you set MARKDOWN_VAULT_MCP_REQUIRED_FIELDS, documents missing any listed field are excluded from the index. The generated index.md and log.md are ordinary documents to the indexer, so the generators give them the fields your vault requires. Your title field is filled in from the file's heading; any other required field is written as null, for you to fill in. Without this the bundle's own listing and change history would be absent from search and list_documents while still opening through read.

Anything already in the file wins, so a title you wrote yourself and the root index.md's okf_version declaration are left alone. On a vault that sets no required fields nothing changes: the reserved files carry no frontmatter, exactly as before.

The enforced write layer

The steps above keep a bundle conformant by convention. The enforced write layer makes the server maintain two of those fields on the writes that go through it. Turn it on by setting MARKDOWN_VAULT_MCP_OKF_WRITE=true. It requires OKF_MODE to be auto or on; pairing it with OKF_MODE=off is a configuration error, since there is nothing to enforce. When the flag is off the write path is untouched, so an ordinary vault behaves exactly as before. On a read-only deployment the flag is accepted and does nothing: the write tools are hidden, so there is no write for the layer to act on.

With the layer on, and only while the vault is an active OKF bundle, every write and edit does two things to the note that lands:

  • Stamps provenance. The server writes generated: {by, at} describing the bytes it just saved. at is the instant of the write, in UTC (2026-09-07T05:32:29Z), as the OKF spec requires of every timestamp. by is human:<subject> when the token carries a usable sub claim, and a tool actor such as markdown-vault-mcp/1.4.0 otherwise. Static bearer credentials, including custom and mapped subjects, use the tool actor. OAuth service tokens carrying sub still receive human attribution; issuer-aware classification is tracked in #1480. It records who called the write, which is not the same as who wrote the words, so an agent writing under your token stamps your identity. Any existing generated value is replaced, including one another tool wrote. The field tracks the last write this server saw rather than the note's history.
  • Invalidates prior review. A content change means an earlier human review no longer describes the current note, so the server clears verified. This fires on any content-changing write, including an edit that touches only a frontmatter line. A rename moves the file without changing its content, so it leaves both fields alone. Clearing is one-way. The entries are removed rather than archived, and a write that changes nothing of substance clears them just the same.

Both fields describe what this server observed on its own write path. A note changed by anything else (a teammate's editor, a git pull, a script) carries whatever its author left there, and the layer neither notices nor corrects that.

The one-shot migration transforms above (okf_convert_links, okf_generate_index, okf_seed_log) are exempt: a mechanical rewrite does not re-stamp provenance or discard a human attestation.

The stamp goes into the note's own frontmatter, so the layer refuses a write whose frontmatter cannot be parsed: there is nowhere to record the provenance it promises. The error names the note and the reason, and nothing is written. Fix the block and write again. With the layer off the write goes through, and the note is left out of the index until its frontmatter parses.

Recording a human review

Enabling the layer also exposes the okf_verify tool. Call it on a note you have reviewed and it appends a {by: human:<subject>, at} entry (at a UTC instant) to that note's verified list, which promotes the note's trust tier to human-reviewed. The verification write is exempt from the invalidation above, so attesting a note does not immediately clear the attestation you just added.

One subtlety is worth understanding before you rely on the human-reviewed tier. The authenticated subject is whose token made the call, not proof that a person read the note. When an agent holds your token and attribution rests on the token alone, the model could promote a note to human-reviewed on its own, and the tier would mean nothing. MARKDOWN_VAULT_MCP_OKF_VERIFY controls how the tool guards against that. It applies only when OKF_WRITE is on. Setting it to a non-default value with the layer off is a configuration error, because the tool is hidden and the setting would have no effect.

  • elicit (the default) makes okf_verify ask you to confirm the review through an MCP elicitation, a prompt your client shows you, and it writes the verified entry only when you answer yes. It fails closed. On modern MCP connections the tool returns an input request and your client repeats the call with your answer; on older handshake connections it uses server-initiated elicitation. When your client cannot show the prompt or you decline it, the call fails and writes nothing. Use a client whose elicitation handler actually presents the question to a person: an automatically affirmative handler would defeat the human-review gate. The recorded subject is your token's sub when available, or local when you use a service credential or no auth.
  • off hides the tool entirely. Use this when reviews are recorded only by tooling outside the server (a CLI step, a git hook, or a CI job that writes verified directly) and you want no in-session path to the tier at all.
  • trust-auth attributes to a token's sub with no confirmation. It refuses static bearer credentials, other client-ID-only identities, and callers without auth. Choose it only when the sole caller of okf_verify is a human-driven interface rather than an agent.

Upgrading bearer-based review clients: deployments that used trust-auth with a static or mapped bearer token must switch MARKDOWN_VAULT_MCP_OKF_VERIFY to elicit and use a client that presents the confirmation to a person. Existing provenance and verification entries are not rewritten.

Whichever mode you pick, human-reviewed means a person deliberately confirmed the review, not that the note is provably correct. Someone can still rubber-stamp a note. The tier promises a deliberate human act rather than diligence, so treat it as one signal instead of a guarantee.

A verified entry is also a claim about the note as it stood when the entry was written. This server clears it when its own write path changes the note, but a change that arrives another way (a teammate's editor, a git pull, a script) leaves the entry in place. On a shared vault, read the tier as a record that someone confirmed a review at that time, and read generated.at beside it.

Keeping log.md and index.md current

With the layer on, the server also maintains a folder's reserved files as a side effect of writing into it. After a successful write or edit on a note, it appends a dated **Update** bullet to that folder's log.md (creating the log, and the day's ## YYYY-MM-DD section, when needed) and regenerates the folder's index.md listing so a new note shows up without a manual step. That covers the upkeep the advisory layer otherwise asks the agent to do by hand, on the write paths below and no others.

A few boundaries keep this predictable, and they are also its limits. Maintenance runs only for content writes on an active bundle. A rename or delete does not trigger it, so a listing can name a note that has moved or gone until the next write into that folder. Neither does a write whose target is itself a reserved file. The okf_verify tool and the one-shot migration transforms are exempt, so an attestation or a mechanical rewrite does not churn the reserved files. Only the folder directly containing the written note is refreshed: if a write creates a brand-new subfolder, its pointer in the parent index.md appears on the next write into that parent. A failure to update a reserved file is logged and skipped; it never fails or rolls back the note write that triggered it, so a listing can silently fall behind.

Do not hand-edit a maintained index.md. The write itself is left alone, because the maintainer skips a write whose target is a reserved file. But the next content write into that folder regenerates the listing from the note list, and an edit you made is gone with nothing reporting it. The same applies to anyone else editing the vault. A teammate's change to a maintained index.md in Obsidian, or one arriving through a git pull, survives only until the next write into that folder. Put anything you want to keep in a note, not in the generated navigation. log.md is appended to rather than regenerated, so hand-written entries there are not overwritten, but a conflicting edit from another editor can still collide with the server's append.

One cost comes with this upkeep. Each write or edit waits for the search index to catch up before it regenerates index.md, so that a just-created note is listed; on a busy vault or with a slow embedding backend this adds latency to the write, bounded at 60 seconds. A refresh failure keeps the existing listing and logs a warning; the note write is not rolled back. Embedding jobs already in the queue can delay the refresh, but it does not wait for embeddings queued after it. On a git-backed vault the reserved-file updates are not extra commits: every write a tool call makes is committed together, so saving a note produces one commit naming the note, its log.md and its index.md.

To stop the upkeep, set MARKDOWN_VAULT_MCP_OKF_WRITE=false and maintain the reserved files yourself. Two things to know before you do. If you had set MARKDOWN_VAULT_MCP_OKF_VERIFY to anything but its default elicit, reset it as well. Startup fails otherwise, because that setting only applies while the layer is on. And disabling the layer stops further upkeep without repairing anything already written. Stamps the layer added stay in their notes, and a log.md that collected repeated frontmatter blocks from a version before 4.2 keeps them. Removing those blocks is a manual edit: the server cannot tell a block it stacked from one the log quotes on purpose, so it leaves all of them alone.

Using OKF with PARA or Zettelkasten

OKF composes with the PARA and Zettelkasten methods. They organise where notes live and how work flows; OKF describes what each note is. A PARA or Zettelkasten vault can also be an OKF bundle. Three points of overlap matter (each method's guide has a matching section from its own angle: Using PARA with OKF, Using Zettelkasten with OKF):

  • Status vocabulary: PARA uses status for workflow state (active, archived), while OKF uses it for lifecycle (draft, stable, deprecated). These mean different things, so keep PARA's workflow state in its own frontmatter key and reserve status for OKF lifecycle. OKF preserves any extra keys you add.
  • Untyped inbox notes: PARA's inbox holds quick captures that are typed later. Give them a placeholder type: Capture so they are conformant from the start, and let triage set the real type.
  • Wikilinks: write in whichever link style you prefer. The server resolves both, and okf_convert_links produces the OKF link style when you are ready to share the bundle.

Exporting a bundle

When you want a copy to share or hand to other OKF tooling, download one through the standard create_download_link tool with a bundle reference rather than a file path:

  • create_download_link with ref="okf-bundle" returns a one-time URL for a zip of the whole vault.
  • create_download_link with ref="okf-bundle:guides" scopes the zip to a folder subtree.

The export reads from the live vault and never changes it. Wikilinks become the root-absolute markdown links OKF recommends. Convention files (_conventions.md) and the template folder are left out, while the reserved index.md and log.md stay in. Non-conformant notes appear as they are.

It is best-effort packaging rather than a certified publication, in three specific ways. It packages what the index currently holds. A note the index left out, whether excluded by pattern, missing a configured required field, or carrying frontmatter that does not parse, is absent from the archive without being reported. It reads the vault as it goes rather than from a point-in-time snapshot, so a write that lands mid-export can leave the archive internally inconsistent; quiesce writes if that matters. And nothing validates the result: the archive carries no conformance report, and building one does not run okf_validate. Run okf_validate yourself before you publish, and read get_index_status.skipped_files to see what the index is leaving out.

Bundle export needs an HTTP or SSE transport with MARKDOWN_VAULT_MCP_BASE_URL set, the same requirement as any transfer link, and it is unavailable when OKF_MODE is off. A git-backed vault also remains a shareable bundle on its own: the repository is an interchange format.