Open-source MCP server for Obsidian
Knapper is a self-hosted MCP server that gives Claude and other agents read and write access to your Obsidian vault. Every write is checked, nothing gets overwritten blind, and anything unsafe stops instead of guessing.
09:14:02 agent-a read → v1 #f04909:14:03 agent-b read → v1 #f04909:14:07 agent-a edit expect #f049 ✓ landed v2 #4f6109:14:09 agent-b edit expect #f049 ✕ changed since your read09:14:10 agent-b read → v2 #4f6109:14:12 agent-b edit expect #4f61 ✓ landed v3 #b319 ── v3 has both changes. Nothing was overwritten.
The problem
Give a few agents access to your vault (Claude on your laptop, Claude Code on another machine, Claude on your phone) and they all edit the same files through Sync. Edits vanish quietly, and nobody tells you.
No conflict, no error, no warning. Two edits simply aren't there.
How it works
You keep using Obsidian and Obsidian Sync. Knapper runs next to a synced copy of your vault, and agents talk only to Knapper. Many agents editing at once becomes one server handling one change per note at a time.
Features
The short version. Each card jumps to where it's shown in detail.
Every edit proves it's working on the latest version.
↓ The raceAgents change the paragraph they mean, not the whole file. Guard text must survive every edit.
↓ Inside a save"No results" means the whole scope was searched. Totals are never guessed.
↓ SearchA Sync conflict or unhealthy sync blocks writes until you look. Reads keep working.
↓ GatesAtomic writes, re-read and byte-compared afterwards. Soft deletes, an audit log and git history.
↓ Inside a saveWikilinks, link checking, your own writing conventions, archived folders.
↓ Built for ObsidianBig or binary files move by a one-time link and a hash check, never through the model.
↓ TransfersNo open ports. Cloudflare Access at the edge, checked again by the server. Notes are data, never instructions.
↓ SecurityNo blind overwrites
Every edit carries a fingerprint of the version the agent read. If the note changed in the meantime, the edit is refused, the agent re-reads, and it tries again. There is no "just overwrite it" tool to misuse.
The agent gets an error and re-reads. You see one extra tool call, not a lost paragraph. Fingerprints are SHA-256 hashes of the note, shortened here.
Inside that save
Every change follows the same path, in the same order. The other nine steps make sure what lands is whole, checked on disk, and logged. Two checks can stop the write, and when they do, nothing touches the disk. Hover or focus a step for what it does.
Stops instead of guessing
When Obsidian Sync creates a conflict copy, or sync stops working, Knapper blocks writes to the affected notes until a human sorts it out. Agents never pick a winner. Reading keeps working the whole time.
Search you can trust
Search is built on ripgrep and every answer says how complete it is. truncated: false means every file in scope was searched. If results are cut off, a cursor picks up exactly where they stopped, with nothing skipped or repeated.
truncated: false means the whole scope was searched.nextCursor resumes a cut-off search exactly where it stopped.scannedFiles is how many files were actually searched.totalMatches is counted, or null if it wasn't. Never estimated.{ "items": [ { "path": "Travel/Packing list.md", "line": 3, "column": 3, "text": "- Ferry tickets (printed)" }, { "path": "Travel/Trip planning.md", "line": 5, "column": 1, "text": "Ferry leaves 07:40, back 18:15." } ], "truncated": false,1 "nextCursor": null,2 "scannedFiles": 5,3 "returnedItems": 2, "totalMatches": 2,4 "changedDuringQuery": false, … }
Built for Obsidian
"items": [{ "check": "broken_anchor", "path": "Garden/Reading list.md", "subject": "Garden log#Tomatos", "line": 3, "column": 22, "message": "'Garden/Garden log.md' has no heading 'Tomatos'" }], "truncated": false
Finds broken wikilinks and heading anchors. Accept the current list once, and after that it reports only what's new.
"path": "Travel/Trip ideas.md", "newSha256": "3e0d3342…", "verified": true, "warnings": [{ "rule": "markdown_internal_link", "message": "this write added 1 markdown link(s) … internal links are [[wikilinks]] here" }]
Tell agents your house rules (wikilinks only, no new tags, where new notes go) and get a warning when a write breaks one.
Files can be added to an archive, but nothing already in it can be changed.
Exact file transfers
Need a PDF or image byte for byte? The agent asks Knapper for a one-time link, its shell downloads the file with curl, and the hash is checked before the file lands. Uploads pass the same checks as any other write, plus a scan for leaked secrets. Off by default.
Security
Your vault never listens on your network. Agents reach it through a Cloudflare Tunnel behind Cloudflare Access, and Knapper checks the Access token again itself, so a mistake at the edge doesn't expose your notes. Paths are confined to the vault. Every action is logged with who made it. A read-only mode lets you start cautiously.
$ jq -r '[.Client, .Op, .Path, .Outcome] | @tsv' audit.jsonl … loopback edit Travel/Trip planning.md ok loopback append Garden/Garden log.md MutationBlocked loopback append Archive/2025 Retro.md PathArchived # refusals are logged too. Behind Cloudflare Access, # Client is the signed-in identity. $ git log --oneline -2 8a62658 knapper snapshot 2026-11-09 02:22:43Z cc1e254 knapper snapshot 2026-11-09 02:22:42Z
Tips for fast agents · optional
Knapper does about 12 ms of work per call. The round trip through the chat app is about 3 seconds. So agents that batch reads and writes feel fast, and Knapper tells them to.
Get started
Generate a sample vault and connect Claude Code. Nothing real involved.
Point it at your vault with Mcp__ReadOnly=true. Agents can look but not touch.
Run knapper verify against your deployment. When it passes and you trust it, turn read-only off.
$ git clone https://github.com/djdelaney/Knapper && cd Knapper # a synthetic sample vault, plus the env for a dev server over it $ tools/dev-vault.sh /tmp/kdev && . /tmp/kdev/env.sh $ dotnet run --project src/Knapper.Mcp # in another terminal $ claude mcp add --transport http knapper http://127.0.0.1:3535/
$ knapper verify --url http://127.0.0.1:3535/ … ok the MCP endpoint completes initialize ok /up and the MCP surface are the same build ok tools/list is EXACTLY the locked surface (18 tools) ok every published tool schema is one a client can load (18 schemas) ok a no-match search still reports exhaustive scan evidence ok the mutation surface is wired and answers with typed codes … 12 ok, 0 failed, 5 skipped all checks passed # skipped: the Access checks, which need the tunnel URL
obsidian-headless