Skip to main content
Glama
README.md
# KnowledgeBaseMCP

An MCP server (stdio) and CLI for [Open Knowledge Format][okf] (OKF) v0.2
bundles: directories of Markdown concepts with YAML frontmatter. It checks,
maintains and imports them. It cares how a concept is written, never what it
says. Local files only. No network. Never writes to git.

One contract, three users: an agent over MCP, CI, and a person at a terminal
with no AI.

Personal project. Apache-2.0, built under the [ASF generative-tooling
guidelines][asf-gt].

<!-- --8<-- [start:run] -->
## Run

Requires [uv](https://docs.astral.sh/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.

```sh
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.

<!-- --8<-- [end:run] -->
<!-- --8<-- [start:layout] -->
## 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:

```markdown
# 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:

```markdown
## 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.

<!-- --8<-- [end:layout] -->
<!-- --8<-- [start:register] -->
## 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"`.

```json
{
  "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:

```sh
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).

<!-- --8<-- [end:register] -->
<!-- --8<-- [start:ci] -->
## Before you push

```sh
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:

```yaml
# .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 -->`.

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

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

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

<!-- --8<-- [end:ci] -->
<!-- --8<-- [start:limits] -->
## 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.

  ```sh
  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.

<!-- --8<-- [end:limits] -->
## Self-check

```sh
uv run test_KnowledgeBaseMCP.py
```

## Spec

`SPEC.md` is OKF v0.2, copied unchanged from
[GoogleCloudPlatform/knowledge-catalog][okf] at commit `bad9bdb`,
Apache-2.0.

[okf]: https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/bad9bdbafa82c672b00d437d4f2cc8f17e9e5f58/okf/SPEC.md
[asf-gt]: https://www.apache.org/legal/generative-tooling.html

TDQS

B3.1/5.0

Scored across 30 tools

Disambiguation3/5

Most tools are distinct, but several lifecycle tools overlap conceptually: move vs migrate vs rename vs split vs archive/unarchive, and verify vs cite vs recheck_apply vs provenance. The descriptions explicitly cross-reference each other, which helps, but an agent could still misselect among these closely related bundle/concept operations.

Naming Consistency3/5

Names are all lowercase and mostly single words, but they mix verbs (search, create, verify, archive) with nouns (status, links, baseline, heatmap, bundles) rather than following a predictable verb_noun pattern. The recheck_plan/recheck_apply pair is the only notable compound style, making the set readable but not consistently patterned.

Tool Count2/5

30 tools is heavy for a knowledge-base server, well beyond the 3-15 well-scoped range. The domain is broad, and many tools are specialized, but the surface feels overloaded with migration, linting, graph, provenance, and lifecycle operations that could likely be consolidated.

Completeness3/5

The surface covers creation, reading, frontmatter editing, linking, linting, fixing, provenance, verification, archiving, migration, and ingestion, which is extensive. However, there is no tool for editing concept body content after creation and no direct delete operation, leaving notable gaps for core authoring workflows.

Maintenance

ActivityMaintained
ResponsivenessNo issues