Open-source MCP server for Obsidian

Let AI agents into your Obsidian vault without losing a note.

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.

Get started View on GitHub
Open sourceSelf-hostedWorks with Obsidian Sync
knap·per Someone who shapes obsidian into tools by striking flakes off it. This one shapes your Obsidian vault into tools for agents.

The problem

Whoever saves last wins.

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.

LaptopClaude · 09:14:02
WorkstationClaude Code · 09:14:05
PhoneClaude · 09:14:09
Garden log.mdafter Sync settles
+ Moved tomatoes to bed 3laptop · gone
+ Linked [[Seed order 2026]]workstation · gone
+ Watered at 7amphone · last save

No conflict, no error, no warning. Two edits simply aren't there.

How it works

One careful door into your vault.

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.

People
LaptopObsidian app
PhoneObsidian mobile
edits
Obsidian Synccloud
Your server
Vault folderobsidian-headless
1 per note
KnapperMCP server
audit loggit historyno open ports
Cloudflare
Accessgate
Agents
claude.ai
Claude Desktop
Claude mobile
Claude Code
People go through Sync.Your apps and your Sync subscription don't change.
Agents go through Knapper.Only. There is no second path to the files.
Both meet at one vault.Edits from agents show up in Obsidian moments later.

No blind overwrites

An edit that hasn't seen the latest version doesn't land.

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.

Without Knapper

last save wins
Agent A
note
Agent B
read → v1
v1
v1 ← read
save v2
v2
v2′
save its v2 on top
✕A's change is gone. B never saw it, and nobody was told.

With Knapper

fingerprinted
Agent A
note
Agent B
read → v1 #f049
v1
#f049 v1 ← read
save, expect #f049 ✓
v2
v2
expect #f049 ✕ changed
v2
#4f61 v2 ← re-read
v3
save, expect #4f61 ✓
✓v3 has both changes. B got an error, re-read, and tried again.

What that looks like in a chat

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.

Chatillustration
Note in Trip planning that the ferry tickets are booked.
vault_editTrip planning.md · expect #f049✕ [PreconditionFailed]
vault_readTrip planning.md→ #4f61
vault_editTrip planning.md · expect #4f61✓ landed
Done. Someone had added hotel details since I first read the note, so I re-read it and added the ticket note under the ferry times. Both changes are there.

Inside that save

The fingerprint check is one step of ten.

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.

✕ refused
nothing written
✕ refused
nothing written
Check that can refuse Refused: the agent gets an error and the note is untouched Receipt returned

Stops instead of guessing

When Sync is unsure, writes wait for you.

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.

Chatillustration
Log today's watering in Garden log.
vault_append[MutationBlocked]'Garden/Garden log.md' has an unresolved conflict sibling ('Garden log (Conflicted copy …).md'). Mutations to both are blocked until a human reconciles.
I couldn't save that. Obsidian Sync made a conflict copy of Garden log, so Knapper is holding writes until you pick which version to keep. I can still read both if you'd like me to compare them.
Per note · conflict copy
✓Writableread ✓ write ✓
conflict copy appears
✕Writes blockedread ✓ write ✕
you reconcile
✓Writableread ✓ write ✓
Whole vault · sync heartbeat
●Healthybeat seconds ago
beats stop
past the limit
✕Writes blockedreads still OK

Built for Obsidian

It understands vaults, not just files.

vault_lint · links
"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

Link checking

Finds broken wikilinks and heading anchors. Accept the current list once, and after that it reports only what's new.

vault_create · receipt
"path": "Travel/Trip ideas.md",
"newSha256": "3e0d3342…",
"verified": true,
"warnings": [{
  "rule": ,
  "message": "this write added 1 markdown
    link(s) … internal links are
    [[wikilinks]] here"
}]

Your conventions

Tell agents your house rules (wikilinks only, no new tags, where new notes go) and get a warning when a write breaks one.

Archive/
├─ 2025 Garden log.md · edit ✕
├─ Old reading list.md · edit ✕
└─ 2026 Trip planning.md · add ✓

Archived folders

Files can be added to an archive, but nothing already in it can be changed.

Exact file transfers

Byte for byte, without the model in the middle.

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.

Model
1ask for a grant → Knapper
3hand link ↓ to shell
file bytes never pass through the model
Agent shell
4curl the link ↓
5 ✓sha256 matches, file lands
Knapper
2one-time link + hash ↑
↗serves bytes once, then the link expires
Cloudflare Accesswho you are, at the edge
Knapper token checkthe Access token, verified again
Host checkonly the expected hostname
Path confinementnothing outside the vault
Vault

Security

This vault holds your whole life. It's treated that way.

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.

audit log + vault history · local dev server
$ 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

The round trip is the cost. Batch.

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.

Knapper's work ~12 ms
Round trip through the chat app ~3 s
3 separate edits = 6 calls, ~18 s
1 batched read + 1 batched write = 2 calls, ~6 s

Get started

An honest path in. Trust it a step at a time.

01

Try it locally

Generate a sample vault and connect Claude Code. Nothing real involved.

02

Deploy read-only

Point it at your vault with Mcp__ReadOnly=true. Agents can look but not touch.

03

Turn on writes

Run knapper verify against your deployment. When it passes and you trust it, turn read-only off.

step 01 · local quickstart
$ 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/
before step 03 · knapper verify · read-only checks
$ 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
To try it
  • macOS or Linux
  • .NET 10 SDK, ripgrep 15+, git
To deploy it
  • A Linux host with a case-sensitive filesystem
  • Obsidian Sync subscription, with obsidian-headless
  • Cloudflare Tunnel and Access