@watilde/ontol
v0.1.0
Published
Minimal, local-first ontology & knowledge graph builder for Markdown and AI agents.
Maintainers
Readme
ontol
Minimal, local-first ontology & knowledge graph builder for Markdown and AI agents.
ontol scans a directory of Markdown files, turns each document into a graph
node, resolves relative links ([x](./y.md)) into directed edges, and
saves the result to .ontol/graph.json. You can then explore the graph from the
CLI, or expose it to an AI agent over the Model Context Protocol (MCP).
- Local-first — everything runs on your machine against plain Markdown. No server, no database.
- OKF v0.1 compatible — reads Open Knowledge Format bundles (reserved
type/description/resource/tags/timestampfields,index.md/log.mdreserved files, bundle-root links), andontol lintchecks conformance. - Agent-ready — ships an MCP server so tools like Claude can search and traverse your notes.
Install
Requires Node.js >= 18.
npm install -g ontol
# or run without installing
npx ontol buildQuick start
# 1. Build the graph from the current directory
ontol build
# 2. List / search nodes
ontol list
ontol search "knowledge graph"
# 3. Inspect a single node and its neighbors
ontol inspect alphaExample
The example/ directory is a small, ready-to-run knowledge base of
interlinked notes. Try it from the repository root:
ontol build example
ontol list example --tag core
ontol inspect knowledge-graph exampleSee example/README.md for what to expect.
How documents become a graph
Each .md file is one node. Fields come from YAML frontmatter, with sensible
fallbacks:
---
id: alpha # optional — defaults to the path relative to root (e.g. notes/alpha.md)
title: Alpha Concept # optional — defaults to the file name
type: concept # OKF: required for conformance (checked by `ontol lint`)
description: ... # optional — OKF reserved, one-sentence summary
resource: https://… # optional — OKF reserved, URI of the underlying asset
tags: [core, demo] # optional — string or array
timestamp: 2026-07-23T00:00:00Z # optional — OKF reserved, ISO 8601
---
Alpha links to [Beta](./beta.md).id is an ontol-specific field (OKF identifies concepts by path/resource);
unknown frontmatter keys are always preserved on the node.
- Nodes — one per Markdown file.
idisfrontmatter.idif present, otherwise the root-relative path. - Edges — one per internal Markdown link, relative (
./x.md,../x.md) or bundle-root absolute (/x.md). Directedsource → target. - Ignored — external URLs (
https://…,mailto:), pure anchors (#section), non-.mdtargets (images), links that escape the project root (../…), and duplicate edges.
The build scans recursively, skipping node_modules/, .git/, .ontol/, and the
OKF reserved files index.md / log.md (these are never turned into nodes).
CLI
ontol build [dir] Build the graph and save to <dir>/.ontol/graph.json
ontol list [dir] [--type T] [--tag T] List saved nodes
ontol search <query> [dir] [--type T] [--tag T] [--limit N]
Full-text search over title/id/tags/content
ontol inspect <ref> [dir] Show one node plus its outgoing/incoming neighbors
ontol lint [dir] [--strict] Check OKF v0.1 conformance (type required, broken links)
ontol mcp [dir] Start the MCP server over stdiodir defaults to the current directory. list / search / inspect require a
graph built with ontol build first.
A node ref (for inspect) is resolved in order: exact id → relative path →
file name → file name without .md. So alpha, notes/alpha.md, alpha.md
all work.
Search ranking
search (and the MCP search_nodes tool) matches the query as a substring and
ranks by where it hit, highest first: title (8) > id (4) > tag (2) > content (1).
An empty query returns all nodes (still honoring --type / --tag), sorted by title.
OKF conformance (lint)
ontol lint checks a bundle against the Open Knowledge Format v0.1:
- error
missing-type— a non-reserved.mdfile has no non-emptytype. - error
invalid-frontmatter— the YAML frontmatter fails to parse. - warning
broken-link— an internal.mdlink points to a file that doesn't exist.
Exit code is non-zero when there are errors (or any warnings with --strict), so
it fits in CI.
MCP server
Expose your graph to an AI agent:
ontol mcp /path/to/notesThe server communicates over stdio (stdout is reserved for JSON-RPC; logs go
to stderr). If no .ontol/graph.json exists yet, the graph is built in memory on
startup.
Tools
| Tool | Description |
|------|-------------|
| search_nodes | Full-text search. Args: query, optional type, tag, limit (default 20). |
| get_node | Fetch one node's frontmatter + body. Args: ref. |
| get_neighbors | List a node's outgoing and incoming linked nodes. Args: ref. |
| rebuild_graph | Re-scan Markdown and rebuild the in-memory graph after edits. |
Claude Desktop / Claude Code config
{
"mcpServers": {
"ontol": {
"command": "ontol",
"args": ["mcp", "/path/to/notes"]
}
}
}Programmatic API
ontol is also a library (ESM):
import {
buildGraph,
saveGraph,
loadGraph,
searchNodes,
resolveNode,
outgoing,
incoming,
} from "ontol";
const graph = await buildGraph("./notes");
await saveGraph("./notes", graph);
const hits = searchNodes(graph, "concept", { type: "concept", limit: 10 });
const node = resolveNode(graph, "alpha");
const links = outgoing(graph, node.id);Graph shape
interface Graph {
version: 1;
generatedAt: string; // ISO timestamp
nodes: Record<string, GraphNode>; // keyed by id
edges: GraphEdge[];
}
interface GraphNode {
id: string;
filePath: string; // root-relative, "/"-separated
title: string;
type?: string; // OKF reserved
description?: string; // OKF reserved
resource?: string; // OKF reserved (URI)
timestamp?: string; // OKF reserved (ISO 8601)
tags: string[];
frontmatter: Record<string, unknown>;
content: string; // body without frontmatter
}
interface GraphEdge {
source: string; // node id
target: string; // node id
kind: "link";
}Development
npm install
npm run build # tsc -> dist/
npm run dev # tsc --watch
npm test # build + node:testLicense
MIT © Daijiro Wachi
