vault-mcp
Provides an MCP gateway to a headless Anki container, exposing Anki operations via JSON-RPC with scope checks, tool blocklists, audit logging, and sync-to-AnkiWeb support.
Provides read and controlled write access to an Obsidian vault, including reading, searching, backlinks, and propose/apply workflows for creating, editing, appending, deleting, and moving notes with path safety, extension limits, and audit logging.
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., "@vault-mcpSearch my Obsidian vault for notes about MCP servers"
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.
vault-mcp
Selbst gehosteter remote MCP-Server, der einem AI-Client (Claude, Claude Code, …) Lese- und kontrollierten Schreibzugriff auf einen Obsidian-Vault gibt – plus einen Gateway zu einem headless Anki-Container. Läuft als Docker-Stack, zum Beispiel auf einem Raspberry Pi im Heimnetz, erreichbar über eine dedizierte Tailscale-Node mit Funnel auf 443.
Authentifiziert über OAuth 2.1 (PKCE) mit Clerk als Identity Provider. Keine Notizinhalte verlassen den Server, nichts wird geloggt, was Inhalt betrifft.
Warum nicht einfach mcp-obsidian?
Die vorhandenen Obsidian-MCP-Server sind meist lokal, unauthentifiziert und dürfen direkt schreiben. Hier ist das Gegenteil der Fall:
Eigenschaft | Umsetzung |
Remote statt localhost | Dedizierte Tailscale-Node |
Auth | OAuth 2.1 + PKCE, dynamische Client-Registrierung, Consent-Seite |
Wer darf rein | Allowlist mit Clerk-User-IDs – leer = niemand |
Schreiben | Standard ist Proposal: Vorschlag mit Diff → Mensch gibt frei |
Audit | Jede Aktion in Postgres, immer mit Pfad, nie mit Inhaltskontext |
Isolation | Container read-only, keine published DB, dedizierte Netzwerke |
Related MCP server: Vault Cortex Obsidian MCP Server
Architektur
Claude / Claude Code
│ OAuth 2.1 (PKCE S256) Streamable HTTP
▼ │
┌────────────────────────────── Tailscale Funnel (443) ─┐
│ app (Next.js 15, node:22-alpine) │
│ │
│ /api/mcp Vault-Tools (Streamable HTTP)│
│ /api/anki/mcp Anki-Gateway (Proxied JSON-RPC)
│ /api/oauth/* register · authorize · token · revoke
│ /.well-known/* OAuth-Protected-Resource, AS-Metadaten
│ /connect /consent /proposals /activity Web-UI │
└──────┬──────────────────────────────┬──────────────────┘
│ network: internal │ network: anki (internal)
▼ ▼
postgres:16 anki (headless Anki +
(nicht published) AnkiConnect + AnkiMCP)
│ network: anki-egress
▼ (festes Subnetz,
nur AnkiWeb-Sync,
Firewall gegen LAN/Tailnet/Pi)Stack: Next.js 15 (App Router) · TypeScript · PostgreSQL 16 + Drizzle ORM ·
@modelcontextprotocol/sdk · Clerk · Tailscale · Docker Compose.
Container & Netzwerke
docker-compose.yml definiert vier Netze, jedes mit klarer Aufgabe:
Netz | Zweck |
| app ↔ postgres. Kein Publish auf den Host. |
| Nur der Tailscale-Sidecar braucht Internet. |
| app ↔ anki. Nie nach außen exponiert. |
| Festes Subnetz |
Ports: App gebunden auf 127.0.0.1:17484 (nur lokal auf dem Host erreichbar), DB gar
nicht published. Extern erreichbar ausschließlich über den Tailscale-Sidecar
(tailscale/serve.json, dedizierte Node, Funnel auf 443).
Authentifizierung & Autorisierung
Zwei geschützte Ressourcen, Tokens gelten nie für beide (Target = "vault" | "anki"):
Anmeldung am Server selbst – Clerk-Login, danach Allowlist-Prüfung (
ALLOWED_CLERK_USER_IDS). Leer = niemand wird eingelassen.OAuth-Client (Claude etc.) – dynamische Registrierung unter
/api/oauth/register, max. 100 Clients.Authorize mit PKCE
S256, Auth-Code läuft nach 5 Minuten ab, Redirect-URI gegen Allowlist (Claude-Hosted-Callbacks + optionallocalhost-Loopback).Consent-Seite zeigt Client, Scope und Ziel; der CSRF-Schutz ist ein HMAC über (User, Client, Redirect, Challenge, Ablaufzeit) – ein Cross-Site-POST kann ihn weder fälschen noch wiederverwenden.
Token-Endpunkt akzeptiert PKCE-Code oder Client-
Basic-Auth; in der DB liegen nur SHA-256-Hashes von Access- und Refresh-Token.Wichtig: Das vom Client angefragte Scope wird nicht vertraut. Verliehen wird das, was der Mensch auf der Consent-Seite gewählt hat,
vault:readimmer dazu.
Scopes
Scope | Bedeutung |
| lesen, suchen, Backlinks |
| Vorschläge anlegen (Standardstufe) |
| direkte Writes ohne Freigabe |
| Anki-Gateway |
Schreibmodell: Proposals
Standard ist die Stufe propose. Ein propose_*-Tool erzeugt keine Datei, sondern
einen Proposal:
prepareChange()normalisiert den Pfad (versteckte/gelesene/schreibbare Ordner, Extension-Whitelist), prüft den Base-Hash der existierenden Datei und erzeugt einen Unified Diff.Der Proposal landet in Postgres mit Pfad, Diff, Grund, Client und Ablaufdatum; optional kommt ein ntfy-Push (nur Pfad und Art, nie Inhalt).
Unter
/proposalsentscheidet der Mensch: annehmen →applyChange(), ablehnen.Beides landet im selben Audit-Log wie direkte Writes.
Direkte Writes (write_note, edit_note, append_to_note, delete_note, move_note)
setzen die Scope-Stufe vault:write voraus.
Read-only-Ordner (VAULT_READONLY_FOLDERS, z. B. Excalidraw/Scripts) sind lesbar,
aber nie schreibbar – für Ordner, deren Notizen Plugins als Code ausführen.
Vault-Zugriff
Alles, was den Vault anfasst, läuft über src/lib/vault.ts
(resolveForRead / resolveForWrite). Der Rest der App berührt das Dateisystem nicht.
Pfade immer vault-relativ,
realpathgegen Symlink-AusbruchExtensions
.md/.canvas/.txtLimits: 1 MB pro Read, 500 KB pro Write
Schreiben atomar: Temp-Datei (
.vault-mcp-*, in.stglobalignorefür Syncthing)rename
Löschen = verschieben nach Obsidians
.trash, also wiederherstellbarVersteckte Ordner (
VAULT_HIDDEN_FOLDERS) sind unsichtbar, auch inlist_notesund Suche
Anki-Gateway
/api/anki/mcp ist ein eigener MCP-Endpunkt, der JSON-RPC an den
headless-Anki-Container weiterreicht. Drei Mann davor:
Scope-Check – nur
anki:full.Tool-Blocklist im Gateway (
src/app/api/anki/mcp/route.ts) plus eine zweite inANKI_MCP_DISABLED_TOOLSim Container (GUI-, Modell- und Deck-Befehle).Audit – jeder Call mit Name und Argument-Zusammenfassung, blockierte Calls ebenfalls.
Der Container selbst hat kein publishtes Port und liegt nur auf Netz anki.
VNC (einmaliger AnkiWeb-Login) ist standardmäßig aus und wird nur gezielt über einen
SSH-Tunnel freigegeben. Ein systemd-Timer synct alle 5 Minuten mit AnkiWeb.
Sicherheitsmodell (Kurzliste)
App-Container:
read_only,cap_drop: ALL,no-new-privileges,tmpfsfür Cache und/tmpPostgres nur im internen Netz, kein Host-Port
App nur auf
127.0.0.1:17484Anki in eigenem Netz mit Egress-Firewall gegen LAN/Tailnet/Pi
App-Rootfs read-only, nur der Vault-Mount ist rw (Writes brauchen das); UID/GID des Containers passen zum Syncthing-Owner auf dem Host
Audit-Log speichert Pfade und Metadaten, nie Notizinhalte
Secrets nur in
.env(gitignored),.env.exampleenthält Platzhalter
Setup
cp .env.example .env # Werte eintragen
docker compose up -d --buildFür NEXT_PUBLIC_* beim Build die echten Werte mitgeben (siehe Dockerfile-Args).
Danach die App-URL als Tailscale-Node mit Funnel erreichbar machen
(tailscale/serve.json).
Lokal entwickeln:
npm ci
npm run typecheck
npm test # node --test, braucht kein echtes Postgres
npm run devHerkunft & Lizenzen
Eigener Code, MIT-lizenziert (siehe
LICENSE).Architektur und Gerüst (Next.js-Struktur, Clerk-Auth, OAuth-Routen, Consent-Seite, MCP-Endpunkt, DB-Schema) sind von cldt-fr/imap-mcp von Clément de Louvencourt abgeleitet – MIT. Danke für die Vorlage. Alles Vault-/Proposal-/Anki-spezifische ist neu entstanden.
anki/ist aus tkober/anki-headless-mcp (Commit8d37894, AGPL-3.0) übernommen und vor Verwendung geprüft: Die Skripte legen nur das Profil an, installieren die Add-ons und schreiben deren Netzwerkconfig. Basis-Bild:ghcr.io/ankimcp/headless-anki. Die MIT-Lizenz des Root-Verzeichnisses gilt nicht füranki/– für dieses Verzeichnis gilt AGPL-3.0.
This server cannot be deployed
Maintenance
Related MCP Connectors
- JustOnceOAuthai.justonce
Persistent memory for AI assistants — one shared, OAuth-secured vault for every MCP client.
OAuth-protected, read-only-by-default MCP server for provenance-labeled QuillCaddie project memory.
Hosted MCP for LearnOS: search courses, lesson citations, flashcards and notes from any MCP client.
AI second brain for people who coordinate several projects. Search and write to your daily notes, tasks and boards, calendar, meeting recaps, documents, clips and an Atlas of people, organizations and projects from Claude, ChatGPT or any MCP client. Free plan, OAuth sign-in.
Related MCP Servers
- AlicenseAqualityAmaintenanceSelf-hosted MCP server for Obsidian with semantic + full-text search over PostgreSQL/pgvector, wikilink graph traversal, atomic note CRUD, OAuth 2.0, and a self-describing vault guide.2518MIT
- AlicenseAqualityAmaintenanceStandalone MCP server for Obsidian vaults - hybrid search (FTS5 + vector + cross-encoder reranking), images and PDFs in agent-readable form, Kanban-aware tasks (Tasks-plugin + Dataview formats), structured memory with topic recall, fine-grained read/write tools for optimal token efficiency, and link graph support. Run locally, self-host, or one-click deploy for remote access. OAuth 2.1.1033175 npm31MIT
- FlicenseNot gradedqualityAmaintenanceSelf-hosted MCP server that provides remote AI clients with read and write access to Obsidian vaults over HTTPS without needing the Obsidian desktop app running.2-
- AlicenseNot gradedqualityBmaintenanceA Model Context Protocol server that gives AI agents bounded, auditable access to propose changes to an Obsidian vault stored in GitHub, with per-agent expiring tokens, read/write/deny path policies, mutability rules, frontmatter checks, and human-reviewed proposal branches.12 npm2Apache 2.0