Skip to content

MCP tools

knomit is MCP-native. A peer connects over the Model Context Protocol and gets a small, sharp tool set whose descriptions carry the guidance a model needs to call them correctly — the descriptions are written as prompt engineering for the calling agent, not as human reference, so no prompt scaffolding is needed to use the tools well.

Knowing when to reach for them is a separate problem, and one the tool descriptions cannot solve: a tool description is only read once the model has already decided to look at that tool. That is what Claude Code’s skills and CLAUDE.md block add.

ToolWhat it does
knomit_learnWrite one or more facts to the knowledge base in a single commit. Deduplicates within a category automatically.
knomit_querySearch by free text, entity, domain, path, or confidence threshold. sort=recent browses by recency; results are paginated.
knomit_explainTraverse a fact’s versioned provenance graph via its refs (paginated BFS), anchored at a commit or at HEAD.
knomit_updateRevise an existing fact’s fields.
knomit_retractRemove a fact. Git history retains the provenance.
knomit_reviewInteractive session to review and maintain the knowledge base (prune, distill, reflect).
knomit_hypothesizeGenerate falsifiable hypotheses from synthesis facts.
knomit_reposList the repos (mounts) behind this endpoint: name, stable repo id, branch, and role.

Each tool is registered with a write flag, and a read-only server drops every tool carrying it. Three tools survive: knomit_query, knomit_explain, and knomit_repos. The other five — knomit_learn, knomit_update, knomit_retract, knomit_review, knomit_hypothesize — are never added to the server, so they do not appear in tools/list at all rather than failing at call time. knomit_review and knomit_hypothesize count as writes because both commit facts.

Read-only is set by KNOMIT_READ_ONLY (or read_only in knomit.toml) and is startup-only — it is read once when the server is constructed and cannot be toggled at runtime. The MCP dispatch routes are exempt from the HTTP method gate, because MCP reads arrive as POSTs; tool filtering is what enforces read-only-ness here. See Deployment for the operator view.

The MCP endpoint is branch-scoped — each agent reads and writes its own branch:

POST /api/v1/repos/{repo}/branches/{branch}/mcp

The agent branch is logged on startup (branch=agent/hostname-abc123) and is also available via GET /api/v1/repos/{repo} (the agent_branch field). Branch names use : in place of / in URL path segments (e.g. agent:hostname-abc123).

A lens — one write repo federated with N read mounts — has its own endpoint and no branch segment, because each mount resolves its own branch:

POST /api/v1/lenses/{lens}/mcp

A ?profile= query parameter on either endpoint is deprecated: the value is logged at debug level and discarded.

There is a single MCP server instance, shared across every repo and lens; each tool handler resolves the target binding from the request context. The instructions sent to the calling model are computed per session from that binding via an AfterInitialize hook, so the model is told the exact topics and categories the repo accepts. The topic list it receives is the repo’s ontology, each topic carried by its description — which makes those descriptions prompt text rather than documentation.

For a lens binding, the instructions also carry a lens addendum: the mounts table, the kb:// path convention, and read coverage per mount. A single-repo binding emits no addendum, so those sessions are byte-identical to before lenses existed.

stdio-only MCP clients (Claude Code, Claude Desktop, Claude Cowork, VS Code, Cursor, …) use knomit-bridge, the stdio↔HTTP adapter built by make build. It auto-discovers the agent branch from the running server, so you never look it up by hand:

{
"mcpServers": {
"knomit": {
"command": "dist/knomit-bridge",
"args": ["--repo", "core"]
}
}
}

--lens <name> connects to a lens endpoint instead; it is mutually exclusive with --repo and skips branch discovery, since each mount resolves its own branch server-side.

See the Quick start for the full connection walkthrough and the streamable-HTTP alternative, and the CLI reference for every bridge flag.

Write one or more facts in a single git commit.

ParamTypeReqDefaultNotes
moment_namestringShort label for this learning moment (becomes part of the commit)
factsarrayOne or more fact objects (below)

Each fact object:

FieldTypeReqDefaultNotes
topicstring✓*Top-level ontology topic. A closed set — an undeclared topic is rejected
categorystring✓*Category path within the topic (e.g. languages/go/concurrency). Not a closed set — declared children guide placement, but anything below a topic is accepted
pathstring✓*Private state only. An explicit repo-relative path under .knomit/<area>/, used instead of topic/category — see private state
titlestringShort descriptive title
bodystringFact body, natural language
kindstringepistemicepistemic (descriptive) or pragmatic (prescriptive)
typestringobservationLeaf type — depends on kind (see below). A JSON-schema enum, so a typo’d type is rejected at the protocol layer
originstringauthored · distilled · discovered. Omit for a fact you write yourself — authored is the default for every type except synthesis, where omitting origin means distilled, so set it explicitly on a synthesis fact you wrote. Immutable after the write
domainstring[]Cross-cutting domain tags
entitiesstring[]Entities the fact mentions
confidencenumber0.70.0–1.0
sourcesinteger1Independent sources
refsstring[]External URLs or source refs

* Either topic and category, or path — never both. Supplying path alongside either is an error, not a precedence rule.

Types — epistemic: observation, concept, process, principle, pattern, reference, synthesis, insight, hypothesis, methodology. Pragmatic: policy, heuristic. A single batch may not mix observed types with inferred types (hypothesis / methodology). The kind, type, and origin vocabularies are generated from one source, so the learn schema, the update schema, and the session instructions cannot disagree.

Schema conformance is not the only gate. If the repo’s ontology declares validation rules, each written fact — and each merged fact after a subsumption — is evaluated against them, and the first rule to fail rejects the write with that rule’s message. knomit_update runs the same check on the reassembled fact.

Returns lightweight rows (title, type, domain, score, ~400-char snippet) — not full bodies. Paginated via an opaque cursor. At least one of text, entities, domain, applies_to, path, type, origin, or min_confidence is required (unless paging with cursor, or using sort=recent).

ParamTypeNotes
textstringFull-text / semantic query
entitiesstring[]All must be present (AND)
domainstring[]Domain tags
applies_tostring[]Ancestor-or-equal domain match (scope includes these)
domain_exactboolExact canonical tag match only (default false)
pathstringPath prefix
typestring[]Filter to epistemic types
originstring[]authored · distilled · discovered
min_confidencenumber0–1
min_similaritynumberCosine floor for text search; 0 = model’s calibrated recall floor
sortstringrelevance (default) or recent (browse, needs no other filter)
limitnumberDefault 20, max 100 (snippet mode); 3/5 with include_body
max_resultsnumberMaximum total results materialised across all pages (snapshot depth). Default 500; higher values are clamped to 500. Page size is limit, not this. A value ≤ 0 is an error
include_bodyboolFull bodies (small pages only)
cursorstringPage token; freezes filters

Explains a fact by walking its versioned provenance graph, anchored at a commit. The root fact is returned in full (with revision history); every referenced fact is returned as a lean summary read at the exact version the referrer pointed to. Summaries may be flagged deleted (retracted since) or superseded (newer at HEAD).

ParamTypeReqNotes
filestringFact path — bare (kb/technology/go/abc123.md) for the write repo, or qualified (kb://<id12>/kb/…) for a fact on a read mount
commitstringAnchor commit; omit for HEAD. Drill into a summary by re-calling with its path + commit
cursorstringSession id to page the walk

knomit_explain never fans out across mounts: the fact’s own path fixes the mount, and the whole provenance walk stays inside it. A kb:// ref is classified as External, not a local fact edge — it carries no evidence weight and creates no local provenance edge.

ParamTypeReqNotes
filestringFact path
moment_namestringLabel for the update
updatesobjectOnly the fields to change

updates fields: title, body, kind, type, confidence, sources, domain (replaces), entities (replaces), refs (replaces). type carries the same enum as knomit_learn, but the update schema deliberately declares no defaults — an omitted field means “leave it alone”, not “reset it”. origin is not updatable.

refs replaces the entire list — it is not appended to. Every ref you leave out of the array is dropped from the fact. To add or refresh one ref, read the fact’s current refs first and resend the full merged list. Omit refs entirely to leave the existing refs untouched. domain and entities behave the same way.

ParamTypeReqNotes
filestringFact path to retract
moment_namestringLabel for the retraction

Retraction is a commit, not a hard delete — the fact remains in history and resolves under commit-anchored reads (e.g. ?fallback=before).

Both knomit_update and knomit_retract reject a kb://<read-mount-id>/… path: read-only mount: repo X is not this binding’s write repo — facts there can only be changed through their own endpoint.

The write destination is a property of the connection — fixed when the bridge starts, resolved from the request, and deliberately not expressible as a tool argument. (A repo: argument would relocate the choice into model output, where a model invents a plausible value rather than asking.) The cost of that design is that a fact written to an unintended repo used to return success identical to one written to the intended repo.

So knomit_learn, knomit_update and knomit_retract each stamp the destination onto their result:

{
"commits": [{"file": "kb/gotchas/x/1a2b3c4d.md", "hash": "abc1234"}],
"written_to": {
"repo": "knomit-kb",
"repo_id": "3ec012f5b4d2",
"branch": "agent/main",
"lens": "knomit-dev"
},
"summary": "wrote 2 facts to repo \"knomit-kb\" (3ec012f5b4d2) on branch \"agent/main\" — the write repo of lens \"knomit-dev\""
}
  • repo is always the write repo, never the lens: a lens reads a union but writes to exactly one member, so naming the lens would misdescribe where the bytes are. lens is reported alongside as the reason that repo was chosen, and is omitted on a repo-bound binding rather than carried as an empty field.
  • repo_id is the 12-hex root-commit id — the same identifier that appears in kb://<id>/… paths and the knomit_repos mount table. It is what makes the stamp discriminating: a repo name is per-machine, mutable, and unique only among one server’s active repos, so two connected knomit servers each holding a repo called knomit on agent/main would otherwise render byte-identically and the stamp would detect nothing. It is omitted when the store is unavailable and the identity is genuinely unknown.
  • The prose summary is not redundant with the struct — a caller reacts to prose, and the point of stamping is that a wrong destination gets noticed in the same turn it happens. The id rides in the sentence for the same reason it is in the struct.

knomit_update and knomit_retract carry the same two fields alongside their own file and commit.

Facts are knowledge. A periodic job’s bookkeeping is machinery, and putting it in kb/ means it gets indexed, embedded, ranked in knomit_query, browsed in the UI, exported by OKF and swept by synthesis.

knomit_learn therefore accepts an explicit path instead of topic/category, and knomit_update, knomit_retract and the equivalent PUT/DELETE /facts/{path} accept the same paths:

Agent-writable = .knomit/<area>/… — at least one subdirectory deep, with <area> a dotless directory name, minus a small reserved list.

  • It is an allow-list rooted in ownership, not a deny-list of foreign dot directories. A deny-list fails open: the moment a KB repo grows .vscode/, knomit would be authorized to write another tool’s territory.
  • <area> is any name the caller picks — nothing in knomit knows what your areas are called, so a new category of private state needs no release.
  • Loose files at the root of .knomit/ are knomit’s own config (the ontology) and stay refused. <area> must be dotless so that a server-owned file’s name cannot be reused as a directory.
  • A writable private path is still private: excluded from discovery at every walker. That is the whole point — job state stays invisible to readers while remaining writable by its job.
  • Ontology validation rules are skipped for these writes, root rules included. Private state has no ontology placement, and without the exemption a root rule would let a job create its slot and then refuse every update to it.

Prune redundant facts, distill clusters into synthesis facts, and reflect on hypothesis transitions to record methodology. Does not generate new hypotheses. A session loop: call with no args to start, then with session_id + response (your JSON decisions for the previous work item) to continue. See Synthesis & hypotheses and Emergent discovery.

ParamTypeNotes
session_idstringOmit to start a new session
responsestringJSON decisions for the previous work item
pagenumberFetch another page of the current item without answering it. Omit when submitting a response; paging advances nothing, so pages may be re-fetched freely
completion_tokenstringEcho back the token from the final page of a multi-page item. Required to answer one — see below. Single-page items carry no token
item_idnumberEcho back item.id from the work item you are answering, so the server can reject a stale answer. Optional and additive — omitting it keeps the old “answer whatever is current” behaviour. It matters because applying a distill item enqueues RAPTOR follow-up items, so the current item can change between render and answer
effortstringnormal (default) · medium · high — medium/high engage the bridge discovery engine
domainstring[]Scope filter on the seed pool (empty = whole corpus)
entitiesstring[]Scope filter on the seed pool

knomit_hypothesize — generate predictions

Section titled “knomit_hypothesize — generate predictions”

Generate new hypothesis facts from synthesis facts on the agent branch. Distinct from review and only to be invoked on explicit user request (never as an auto-follow-up). Per work item the model decides whether to write a hypothesis; skipping is the expected outcome for most.

ParamTypeNotes
session_idstringOmit to start
responsestringAcknowledgement for the previous work item
item_idnumberEcho back item.id from the work item you are answering, so the server can reject a stale answer. Optional and additive
effortstringnormal (default) · medium · high — medium/high engage the backward (keystone) bridge engine
domainstring[]Scope filter (empty = whole corpus)
entitiesstring[]Scope filter

knomit_repos — list the mounts behind this endpoint

Section titled “knomit_repos — list the mounts behind this endpoint”

Read-only and parameterless. Returns the binding and one row per mount:

{
"binding": "core",
"mounts": [
{"name": "core", "id": "a1b2c3d4e5f6", "branch": "main",
"role": "read+write", "write_branch": "agent/hostname-abc123"},
{"name": "vendor-kb", "id": "0f9e8d7c6b5a", "branch": "main", "role": "read"}
]
}
FieldNotes
nameRepo name
id12-hex prefix of the repo’s root commit — the same id that appears in kb://<id>/… paths
branchThe branch this mount is read at
roleread+write on the write mount when writes are permitted; read otherwise
sourceSource slug, when the mount has one
write_branchPresent only on the read+write row — where writes actually commit, which may differ from branch

This is how a session discovers its source slug and its repo ids; it replaces the bridge’s removed --source flag.