Guide#

AI-generated

Drawn from the README, written with Claude Opus 5.5, under human review.

Run#

Requires uv.

A knowledgebase is wherever you point the tool: KB_ROOT, else the current directory. It is one bundle at its root, or a set of sibling bundles. A directory whose index.md declares okf_version is a bundle root; bundles do not nest. An index.md at the knowledgebase root lists its bundles (index apply=true there writes it), and tools find the root from inside a bundle through it. bundles lists them, split makes a directory its own bundle, rename moves or renames one, migrate moves a concept between bundles, archive and unarchive retire and restore a concept or a whole bundle, ingest keep_okf=true merges one in, and upgrade converts OKF v0.1.

Tools follow where you are: in a bundle, that bundle; at the knowledgebase root, every bundle (lint, fix, baseline, status, list, search, recheck_plan). Name files or directories to lint just those (lint notes/a.md docs/). Files are named the OKF way: a bundle and the path within it (bundle=eng file=runbooks/deploy.md), in every input and output. On the command line, without bundle=, paths are relative to where you are. Write tools act in the bundle the file belongs to. In a git work tree, files git ignores are skipped.

wget https://raw.githubusercontent.com/dfoulks1/KnowledgeBaseMCP/main/KnowledgeBaseMCP.py
uv run KnowledgeBaseMCP.py help                 # every tool, one line each
uv run KnowledgeBaseMCP.py help move            # one tool: what it does, its arguments
uv run KnowledgeBaseMCP.py lint format=text
uv run KnowledgeBaseMCP.py init apply=true       # start a new bundle

Any tool runs as <name> name=value ... or <name> '<json object>'. Bare arguments fill the required ones in order (move a.md b.md); on set, any other key=value is frontmatter (set a.md status=deprecated tags=[x,y]). Write tools that touch many files default to apply=false and show a diff.

--attest records you, as human:<KB_USER> (else your git user.email), on create and provenance (generated), verify and recheck_apply (verified). It exists only on the command line: no MCP call can pass it.

Output is JSON; format=text prints one key per line and diffs as is.

Exit codes: 0 ok, 1 findings, 2 usage error.

Layout#

kb/                          the knowledgebase (KB_ROOT)
  index.md                   lists the bundles; no frontmatter
  .gitattributes             log.md and index.md merge=union
  eng/                       a bundle
    index.md                 okf_version, then the list of its concepts
    log.md                   its history (§9)
    runbooks/
      index.md               optional: this directory's concepts
      deploy.md              a concept
  archive/                   the archive: a knowledgebase of its own, set apart
    index.md                 lists the archived bundles
    eng/old-runbook.md       archived from eng (archive/eng is a bundle)

index.md#

OKF §8: an index lists what is in its directory, for progressive disclosure. Every index has the same body shape, sections under headings with one entry per item:

# Engineering

Hand-written text is kept.

<!-- kb-index:start (generated by KnowledgeBaseMCP index; do not edit between the markers) -->

### runbooks

* [Deploy](./runbooks/deploy.md) - How we deploy.

<!-- kb-index:end -->
  • The tool owns what is between the markers and rebuilds it from frontmatter (title, else first heading, else file name; description, else the first sentence). Write anything else around them.
  • Knowledgebase root: no frontmatter (it is not a bundle). The list has one entry per bundle, titled by that bundle's first heading. Tools find the knowledgebase by walking up from a bundle to the nearest index.md that links to it.
  • Bundle root: frontmatter holds only okf_version: "0.2", which is what makes the directory a bundle (§12). The list covers every concept, a section per top-level folder.
  • Any other directory: no frontmatter. The list covers that directory's concepts and subdirectories (index dirs=true writes one everywhere).
  • Being listed counts as being linked, so a listed concept is not an orphan.
  • get synthesizes a missing index from frontmatter (§8 allows this).

log.md#

OKF §9: date headings, newest first, one entry per line. Each tool write adds an entry to the nearest log.md above the change, with a hidden unix-time stamp:

## 2026-10-27

* <!-- 1761588000 --> **Update**: Moved deploy.md to [ship.md](/runbooks/ship.md).

record "note" writes one by hand (link the concept it is about). log file=x.md reads a concept's history: the entries linking to it in every log.md above it, and the git commits that changed it. log alone shows the bundle's recent entries.

init adds log.md merge=union and index.md merge=union to the knowledgebase's .gitattributes, so git keeps both sides when parallel branches log or index. After such a merge, fix merges duplicate log date sections, orders stamped entries by time, and rebuilds the generated index lists. A section with a hand-written, unstamped entry keeps its order.

The archive#

archive/ is a knowledgebase within the knowledgebase, and a special one. Its index.md lists its bundles, like the knowledgebase root's. archive file=x.md bundle=eng moves a concept to archive/eng/x.md (starting archive/eng as a bundle); archive bundle=eng moves the whole bundle to archive/eng/, still a bundle. unarchive eng/x.md or unarchive eng puts them back. Links follow both ways.

The live knowledgebase does not see the archive: lint, list, search, status and web leave it out, nothing in it counts as nesting, and lint warns on links into it (archived_link). Editing tools refuse archived concepts. That is the only enforcement: the files are plain files, so an editor or a commit can still change them. It works whether the knowledgebase root is a set of bundles or a bundle itself.

Register with an MCP client#

Add this to your MCP client's servers file: .mcp.json in the project for Claude Code, claude_desktop_config.json for Claude Desktop, .cursor/mcp.json for Cursor. VS Code's .vscode/mcp.json takes the same entry under "servers".

{
  "mcpServers": {
    "knowledgebase": {
      "command": "uv",
      "args": ["run", "/path/to/KnowledgeBaseMCP.py"],
      "env": { "KB_ROOT": "/path/to/kb", "KB_USER": "your-id" }
    }
  }
}

KB_ROOT is the knowledgebase (else the client's working directory). KB_USER prefills your id in confirmation prompts. Or, in Claude Code:

claude mcp add knowledgebase --env KB_ROOT=/path/to/kb -- uv run /path/to/KnowledgeBaseMCP.py

The client loads the tool list at start. After editing the script, restart the client or reconnect the server (/mcp in Claude Code).

Before you push#

uv run KnowledgeBaseMCP.py lint format=text      # what's wrong, each with a fix
uv run KnowledgeBaseMCP.py fix apply=true        # mechanical repairs, then re-lint
uv run KnowledgeBaseMCP.py lint format=text      # what's left needs you

scope=okf limits the check to the spec; scope=house to hygiene (links, anchors, orphans, islands, stubs, duplicate titles, reference links, filenames, tags, generated indexes). only= and ignore= keep or drop finding kinds: lint only=stub,island.

CI#

Vendor KnowledgeBaseMCP.py into the bundle's repo, then:

# .github/workflows/okf.yml
name: okf
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # provenance needs full history
      - uses: astral-sh/setup-uv@v6
      - run: uv run KnowledgeBaseMCP.py lint scope=okf format=github

Findings show as annotations on the PR. format=sarif suits code scanning. On PRs, since=origin/${{ github.base_ref }} limits findings to the files the PR changed. On a large existing collection, commit a baseline apply=true snapshot and run lint baseline=.okf-baseline.json, so only new findings fail. The snapshot sits at the knowledgebase root and matches from any directory. CI lints; it does not push fixes (fork PRs get a read-only token anyway).

To skip files outright, list them in a kbmcp.nolint file. It works like .gitignore: it may sit in any directory, patterns are relative to it, the last match wins and ! re-includes. At the knowledgebase root it covers every bundle. Skipped files are still read, so links to them resolve. One file can skip itself with the line <!-- kbmcp-nolint -->.

# kbmcp.nolint: agent instructions are not concepts
AGENTS*
CLAUDE.md
.kiro/

pre-commit, lint only (a hook that edits files leaves the commit unstaged):

repos:
  - repo: local
    hooks:
      - id: okf-lint
        name: OKF lint
        entry: uv run KnowledgeBaseMCP.py lint format=text
        language: system
        pass_filenames: false

Reading the knowledgebase#

  • Find things in three steps, each small: dirs maps the bundle (each directory and how many concepts it holds); search ranks concepts by where every word matches (title, then id, tags, type, description, body), with type=, tag= and dir= filters; get reads only the top hit. vocab lists the tags and types in use, the words those filters take.
uv run KnowledgeBaseMCP.py dirs format=text
uv run KnowledgeBaseMCP.py search deploy rollback dir=runbooks format=text
uv run KnowledgeBaseMCP.py get runbooks/deploy.md format=text
  • web maps links and citations: bundles at the knowledgebase root, concepts in a bundle. format=text for the terminal, format=mermaid for anything that draws Mermaid (GitHub, GitLab).
  • heatmap days=30 ranks concepts by how many commits changed them (the ones most likely to collide across branches) and by how many people did; who changes the most; and concepts only one person has ever touched. index.md and log.md are left out.
  • status lists what needs a person next: longest since review, oldest drafts, concepts without sources, the longest concepts, and what goes stale within due_days; the most linked-to concepts (hubs); and the pages links want that do not exist yet (wanted), most wanted first.
  • log file=x.md is a concept's history.

Known limits#

  • A human verify over MCP needs a client that supports elicitation (confirmation prompts); otherwise the human runs it from the CLI.
  • move, migrate, split, rename, archive and unarchive rewrite links (code blocks included; read the diff) and §6.2 path fields (resource, computation, executor/attester, sources[].resource). A value naming an existing file is a path; one naming nothing is a path only if it has a file extension or a leading /, ./ or ../, else a scope description.
  • OKF makes okf_version optional; here it is how a bundle is found. An undeclared bundle is any folder until you pass it as bundle=.
  • ingest does not convert other formats or check licenses. Its plan says so.