vaults
Provides tools for managing Obsidian-style Markdown vaults on disk: listing vaults and notes, reading and writing notes with frontmatter, wiki-links and backlinks, full-text search, tag counts, and version history/restore via local git.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@vaultssearch my vaults for meeting notes from yesterday"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
vaults-hub
A Python MCP server that exposes your Obsidian-style Markdown vaults to MCP clients (e.g. OpenCode).
Keep each project's docs as plain Markdown on disk, and let an agent read and maintain them through a small, predictable tool API — no Obsidian plugins, windows, ports, or API keys. Every change is auto-versioned with local git, so nothing is ever truly lost.
Quickstart
The fastest way — no clone, no venv. Published on npm as
vaults-hub:
npx -y vaults-hubThis needs Python 3.10+ on PATH (or VAULTS_HUB_PYTHON set to its
path); CI tests 3.10, 3.12 and 3.14. On first run it bootstraps the pinned
dependencies from requirements.txt into a cached venv (one-time, ~30s;
later runs start instantly); see Configuration for the
cache location.
From source (offline/dev alternative) — prereqs are Python 3.10+
(developed/tested on 3.14), a POSIX platform (Linux/macOS — locking uses
fcntl), and optionally ripgrep
(rg) for faster search (a pure-Python fallback is used when rg is
absent):
git clone https://github.com/ishaan-jindal/vaults-hub.git vaults-hub
cd vaults-hub
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python server.py # stdio MCP server (no HTTP port)Vaults live in ~/.vaults by default (each subdir is one vault, e.g.
~/.vaults/MyProject/), auto-created on startup when missing. To use a
different location:
export VAULTS_ROOT="$HOME/my-notes"
python server.py --vaults-root "$HOME/my-notes" # flag wins over env varRegister with OpenCode
Add to your opencode.jsonc (adjust paths to your machine — do not
copy any hardcoded home directory). The npx form is recommended:
{
"mcp": {
"vaults": {
"type": "local",
"command": "npx",
"args": ["-y", "vaults-hub"],
// Pick ONE way to point at your vaults (flag wins over env var):
"environment": {
"VAULTS_ROOT": "<path-to-your-vaults>"
}
}
}
}The local-venv form is the offline/dev alternative (replace with the actual paths on your machine):
{
"mcp": {
"vaults": {
"type": "local",
// Replace with the actual paths on your machine:
"command": ["<path-to-vaults-hub>/.venv/bin/python", "<path-to-vaults-hub>/server.py"],
// Pick ONE way to point at your vaults (flag wins over env var):
"args": ["--vaults-root", "<path-to-your-vaults>"],
"environment": {
// ...or via env var instead of "args" above:
"VAULTS_ROOT": "<path-to-your-vaults>"
}
}
}
}Related MCP server: obsidian-kb
Usage examples
Vault layout: each project is a directory directly under VAULTS_ROOT
containing .md files, e.g. <VAULTS_ROOT>/MyProject/MyProject.md.
With the server registered as vaults:
List vaults: call
list_vaults→[{"name": "MyProject", "path": "...", "notes": 12}]Read a note:
read_note(vault="MyProject", path="MyProject.md")→ frontmatter, content, wiki-links, backlinksWrite safely:
read_note→ edit →write_note(..., expected_sha256="<sha from read>")to avoid clobbering concurrent editsSearch:
search_notes(query="build command", vault="MyProject", before=1, after=1)Recover a note:
history(vault="MyProject", path="Notes.md")→ pick asha→restore(vault="MyProject", path="Notes.md", rev="<sha>")
Tools
14 tools (see vaults/server.py for exact descriptions). Read-only tools are
marked R, mutating tools M (every note mutation honors expected_sha256
for optimistic concurrency — read first, then pass the sha back; delete_vault
is gated on confirm instead):
Tool | R/M | What it does |
| R | List all project vaults with note counts |
| R | Server version, Python/platform, git + ripgrep availability, |
| M | Create a new vault ( |
| M | Delete a whole vault including git history (irreversible; |
| R | List dirs/notes under a vault path ( |
| R | Read a note: frontmatter, content, wiki-links, backlinks, unresolved links |
| M | Create or atomically overwrite a note ( |
| M | Append text to a note (created if missing), atomic write, optional |
| M | Delete a note ( |
| M | Move/rename a note (copy+delete, not atomic); errors if src missing or dst exists; honors |
| R | Tag counts per vault from cached frontmatter (one vault or all) |
| R | Full-text search via ripgrep (or Python fallback) across one vault or all, with before/after context lines; |
| R | Version history for a note ( |
| M | Restore a note from a past revision (snapshots dirty state first; recreates deleted notes); echoes the revision as |
Also built in: YAML-frontmatter parsing with auto-refreshed updated: dates,
file locking (fcntl), atomic writes via temp-file + rename, backlink/tag
indexes with mtime invalidation, and optional debug logging to file.
Tool annotations
Every tool declares all four MCP ToolAnnotations hints explicitly, so
hosts that gate on them (OpenAI's tool directory rejects a tool where any
hint is missing or non-boolean) accept the whole set:
Hint | Meaning here | Value |
| does not modify the vault |
|
| may destroy or overwrite existing data |
|
| repeat calls add no further effect |
|
| touches entities outside the vaults root |
|
The hints are hints, not guarantees: a client must not make trust decisions
from them alone. They matter because the stdio writer serializes tool
payloads with exclude_none=True, so an unset hint is dropped from
tools/list entirely rather than sent as null. smoke_test.py asserts
all four are present booleans on all 14 tools, so a new tool cannot
silently reintroduce the gap.
Reliability
How the server avoids losing or corrupting notes:
Atomic writes. Every note write goes to a temp file in the same directory (
fsynced, thenos.replace), with the parent directoryfsynced afterwards — readers never see a half-written note. Existing file permissions are preserved.Locking. Mutations serialize per note (
flockread-modify-write cycles across processes) and git operations serialize per vault, with a consistent lock order everywhere (note lock first, git lock inside it), so concurrent writes and restores cannot deadlock. Read-onlyhistorytakes no locks and never blocks writers; truncation flags are computed by the core library, not the tool boundary.Optimistic concurrency. Every mutation (
write/append/move/delete/restore) acceptsexpected_sha256; a stale sha is rejected instead of clobbering someone else's edit.delete_vaultis instead gated onconfirmexactly equaling the vault name.Fail-open versioning. The write itself is the source of truth — a missing git binary or failed commit warns (surfaced as
commit_error) but never blocks the write.Search parity.
search_notesprefersrg --jsonand falls back to pure Python when ripgrep is absent; both sides skip dotfiles and.obsidian/so hidden files never leak into results.No surprise frontmatter. Notes without a frontmatter block stay that way;
updated:is only refreshed (or inserted) when a block exists.Fresh indexes. Backlink/tag indexes are invalidated by mtime, so external edits are picked up on the next read.
Symlink confinement. Symlink targets resolving outside the vault are rejected — a link can never pull reads or writes out of the vault.
Configuration
Variable | Default | Purpose |
|
| Root dir; each immediate subdir is one vault |
| unset | Set to |
| unset (versioning on) | Set to |
Precedence for the vaults root: --vaults-root <dir> flag > VAULTS_ROOT
env var > default ~/.vaults. Whatever wins is auto-created on startup
when missing.
Notes:
ripgrep is optional. If
rgis onPATH,search_notesusesrg --json; otherwise a pure-Python fallback (with minimal.gitignorehandling) is used. No configuration needed either way.POSIX-only. File locking uses
fcntl, so Windows is not supported.npx launcher.
VAULTS_HUB_PYTHONoverrides which Python thevaults-hubbin uses (elsepython3, thenpythononPATH; must be 3.10+). When that interpreter lacks the pinned dependencies, the bin installs them once into a cached venv under$XDG_CACHE_HOME/vaults-hub(else~/.cache/vaults-hub), keyed by Python minor version, and reuses it silently while therequirements.txthash matches.
Versioning
Each vault is a local git repo (one per vault root), maintained automatically:
Default-on. The repo is lazily
git init -b mainon the first mutation; every write/append/delete/move commits path-scoped with aSha256:trailer (e.g.vaults: write Notes.md). The write itself is the source of truth — a missing git binary or failed commit warns but never blocks the write.Local-only, no push. Versioning never pushes, pulls, or fetches; identity is per-invocation (
vaults-hub), never written to gitconfig. Lock/tmp files and.obsidian/are excluded via$GIT_DIR/info/exclude(no visible.gitignore). A vault already nested inside an external repo is left alone.Opt-out. Set
VAULTS_HUB_GIT=0to disable init/commits;historyandrestorethen return clear errors.history(vault, path?, limit?)lists[{sha, date, message}](newest first, limit clamped 1–200) plus atruncatedflag when more commits exist beyond the page; an untracked path returns[]withuntracked:true.restore(vault, path, rev, expected_sha256?)writesgit show rev:pathback to disk atomically (snapshotting dirty state first), echoes the revision asrestored_from, and can recreate deleted notes.
Testing
No test framework — one end-to-end smoke test over a temporary stdio server:
source .venv/bin/activate
python smoke_test.pyIt creates fixture vaults in a temp dir, exercises all tools (including versioning and its opt-out), and exits non-zero with a traceback on failure.
Architecture (brief)
opencode (MCP client, stdio) <-> vaults/server.py (FastMCP "vaults", 14 tools) <-> ~/.vaults/<project>/*.mdvaults/is the whole server:config.py(vaults root, logging, CLI),notes.py(path validation, UTF-8 checks,fcntllocks, atomic writes, SHA-256 optimistic concurrency, frontmatter/wiki-link helpers, note CRUD, vault creation),indexes.py(in-memory backlink/frontmatter indexes with mtime-based invalidation),search.py(ripgrep + Python-fallback search),versioning.py(per-vault git layer), andserver.py(FastMCP app, 14 tools, stdio bridge). Rootserver.pyis a thin shim sopython server.pykeeps working.server.pykeeps the event loop responsive: every tool runs its blocking library call in a worker thread viaanyio.to_thread. Errors are sanitized at the tool boundary (client-safeValueErrors pass through; anything else becomes a plain failure notice) while the server log keeps the full traceback; request logging records only vault/path.smoke_test.pyspawnsserver.pyover stdio withVAULTS_ROOTpointed at a temp dir.
Limitations
Private SDK pin: dependencies are pinned in
requirements.txt(anyio==4.9.0,mcp==1.30.0,PyYAML==6.0.3); bump deliberately and re-runpython smoke_test.py.Move is copy + delete, not one atomic rename:
move_notewrites the destination atomically, then unlinks the source — a crash between the two steps can leave both copies behind. A failed move cleans up the partial destination. It also refuses to overwrite an existing destination.Wiki-link resolution is name-based: bare
[[Name]]links resolve via a stem index (shortest path wins on collision); only[[path/with/slash]]links resolve as paths. Self-links are excluded from backlinks.POSIX-only (
fcntllocking); no Windows support.No authentication: the stdio transport trusts the local client; do not expose vault contents beyond your machine without adding your own access controls.
License
MIT — see LICENSE.
Contributing
See CONTRIBUTING.md for setup, checks, and the PR flow.
This server cannot be deployed
Maintenance
Related MCP Connectors
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
MCP-native open-source Notion alternative: read & write pages, databases and kanban boards.
Google Keep-style notes app with an MCP server for AI agents to read/write notes.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server that provides read and write access to an Obsidian vault by interacting directly with markdown files on disk. Supports searching, listing, reading, creating, editing, and appending notes without requiring any Obsidian plugins.3,358 npmISC
- FlicenseCqualityDmaintenanceGit-backed MCP server for creating and maintaining an Obsidian-style markdown knowledge base with full CRUD, search, and git sync.7-
- FlicenseCqualityCmaintenanceMCP server to query and modify an Obsidian vault or any folder of markdown files. It provides search, tag filtering, backlinks, and CRUD operations on notes, with path traversal protection.11-
- AlicenseNot gradedqualityBmaintenanceMCP server for browsing Obsidian-compatible Markdown vaults, searching notes, viewing backlinks and knowledge graphs, and adding selected content to chat context.8 npmMIT