self-hosted · markdown-first · REST + MCP
doction¶
A markdown-first knowledge base for humans and coding agents. Write plain markdown in the browser, keep every change in git, and let your agents search, read and write the same pages over a REST API or a native MCP server — on your own hardware.
Why doction¶
Engineering knowledge ends up split between a wiki people read and whatever an agent was told in its last prompt. doction keeps one copy: pages are plain markdown in a git repository, search runs locally, and an agent reaches the same pages a person does through a standard interface.
It is boring on purpose. doction does retrieval — the language model lives in your agent, not here. There is no LLM, no API key and no SaaS inside it.
Built for humans and coding agents¶
-
Markdown
Pages, workspaces,
[[wikilinks]],#tagsand a frontmatter block. Rendered in the browser from the markdown as stored. -
API-first
The server is JSON only. The same endpoints serve the browser,
curland an agent. -
MCP
A native MCP server at
/api/mcpwith 27 tools, from search and RAG context to writing one section of a page. -
Search
PostgreSQL full-text search by default, with opt-in local semantic search that runs fully offline.
-
Knowledge graph
Wikilinks become edges. Backlinks, broken links, orphans and a drawable graph of the whole workspace.
-
Self-hosted
An app container and a Postgres container. Multi-arch image, amd64 and arm64, with no external request at runtime.
How it works¶
Human Coding agent Script
(browser) (MCP) (curl)
│ │ │
/app /api/mcp /api
└───────────────┼───────────────┘
│
doction
│
markdown in git · Postgres · embeddings
│
knowledge · search · graph · history
The browser app and scripts use the REST API under /api; agents use MCP at /api/mcp.
On every save, doction commits the page to git and extracts its frontmatter, tags and
wikilinks into indexed tables. With semantic search on, a background worker chunks and embeds
the page without blocking the save. The details, and why the odd-looking decisions are what
they are, are in Architecture.
Features¶
- Per-page git history — every save is a commit; browse diffs and previous versions.
- Full-text search — PostgreSQL
tsvector/GIN with accent folding, ranked byts_rank. - Local semantic search (opt-in) — ONNX MiniLM embeddings baked into the image, no API keys; degrades to full-text search when off.
- A graph you can see — wikilinks are followable, every page lists what links to it, and the graph view draws the workspace with orphans and broken links called out.
- Local ML, no LLM — link and tag suggestions, near-duplicate detection, extractive summaries and workspace insights, computed with numpy.
- Shared workspaces — collaborators join as
ownerormember. - Outgoing webhooks — page events delivered to a URL you control, signed with HMAC-SHA256.
- OCR for uploads (opt-in) — tesseract indexes the text inside pasted screenshots.
API + MCP¶
doction is consumed by programs in two ways, over the same pages and the same auth.
claude mcp add --transport http doction $DOCTION/api/mcp \
--header "Authorization: Bearer doction_..."
Self-hosted¶
doction runs as two containers — the app and Postgres — on modest hardware you control. Everything the browser loads is served by your instance, so it works on a LAN, over a VPN or with no route to the internet. Your pages stay plain markdown in a git repository you own.
Installation covers Docker Compose, a plain docker run behind a reverse proxy,
and running from source. Configuration lists every environment variable
and what breaks if you skip it.
Architecture¶
A FastAPI application, a Postgres database and a git repository of markdown files. That is the whole system. Architecture explains how the three relate: where a page lives, how search is built, why the schema converges instead of migrating, and why there is no ORM.
Documentation¶
Using doction¶
| Page | What it answers |
|---|---|
| Writing pages | Creating pages, the tree, renames, history, and getting a deleted page back |
| Linking pages | [[wikilinks]], what resolves as a target, and why a broken link is kept |
| Tags and metadata | #tags, the block at the top of a page, and the one spelling of it that is silently ignored |
| Searching | The three modes, and what a query actually does to what you typed |
| The graph | The link graph, its limits, and the insights that read it without drawing it |
Developer¶
| Page | What it answers |
|---|---|
| REST API | Every endpoint the application serves, grouped, with the auth each needs |
| Agents and MCP | Connecting an agent, the auth model, and which of the 27 tools to reach for |
| Architecture | How the pieces fit, and why the odd-looking decisions are what they are |
Running doction¶
| Page | What it answers |
|---|---|
| Introduction | Screenshots and a quick start |
| Installation | Getting a working instance, from Compose to a bare docker run behind a proxy |
| Configuration | Every environment variable, what it does, and what breaks if you skip it |
| Operations | Upgrades, backup and restore, logs, capacity, what to do at 3am |
| Troubleshooting | Symptom-first index of the failures people actually hit |
Project¶
- Contributing — local setup, the check gate, and how to get a change merged.
- Security — the security model, the reporting channel, and the hardening checklist.
- Design — the visual system, describing what is implemented.
- Changelog — what changed per release.
evals/results/— the retrieval measurements behind the search constants: why the reranker ships off and why the stemmer is English.
Conventions used here¶
- Commands assume the container. Prefix with
docker compose exec doctionif you are running the Compose stack. $DOCTIONis the base URL of your instance, e.g.https://wiki.example.com.- Anything marked opt-in is off unless you set its flag. doction starts with everything expensive disabled.
Get started¶
Two containers, one .env, and a first user.