keyword_searchLABSCO SUMMARY
Writes are staged and published atomically — a crash cannot truncate a note.
The engineering here is unusually explicit about failure. Every write stages bytes in a temporary inode, fsyncs, and only then publishes: creation uses a kernel-atomic hard link that refuses to clobber, and the soft delete uses a single non-replacing rename. Where the filesystem cannot do that, the operation refuses with a named error rather than degrading to something that can overwrite. The system checks are the same story — an old kernel or an old pgvector makes it exit at startup and say which one, instead of quietly returning worse search results. The requirements list is long, and it is long on purpose.
What it is
A self-hosted server that indexes an Obsidian vault into PostgreSQL with pgvector and serves it over MCP. Agents read what you read, follow the same wikilinks, and pick up your folder layout, frontmatter schema and tag conventions on the first call.
What you get
- 25 tools across five families. Search:
keyword_searchover PostgreSQL tsvector,semantic_searchvia pgvector,list_notes,get_recent,get_tags, andget_vault_guidewhich serves your vault's own CLAUDE.md live - Read and write:
read_notebounded by MAX_READ_RESPONSE_CHARS withsectionfor one heading andoffsetto continue;create_noterefuses overwrite;edit_notehas four exclusive modes — full replace, append, find/replace and section — withdry_run=Truereturning a unified diff move_noteoptionally rewrites incoming[[Old]],[[Old|alias]]and![[Old]]references;delete_notesoft-deletes into.trashby default;set_frontmattermutates YAML leaving the body byte-identical- Non-markdown files get peers rather than overloads:
read_filereturns text, an inline image block, or base64;write_file,list_filesanddelete_fileround it out — all sharing the path-traversal guard that excludes dot-directories - File transfer closes the gap MCP cannot:
request_uploadmints a single-use link bound to one destination path,check_uploadreports its state,request_downloadmints a save link, andimport_from_urlfetches a public https asset under a deny policy that blocks private, loopback, link-local and metadata addresses at every redirect - Wikilink graph:
get_backlinks,get_linkswith resolved and dangling entries,get_neighborhoodas an undirected BFS,find_relatedby averaged chunk embeddings, andfind_orphans - API keys carry
readorreadwritescopes and write tools refuse on a read-only key; OAuth 2.0 with PKCE covers ChatGPT, Claude Desktop and claude.ai; every call is logged with name, params, duration and calling credential
Requirements
Docker and Docker Compose on Linux, kernel 5.6 or newer — every directory below the vault root is opened with a single openat2 call, so the kernel proves a write stayed inside the vault, and there is no fallback. Kernel 5.8 is needed for the file-transfer tools; below that they refuse and everything else works. PostgreSQL 16 with pgvector 0.8.0 or newer, and either an Ollama instance running bge-m3 or an OpenAI API key. The filesystem must be case-sensitive and support hard links and RENAME_NOREPLACE. macOS and Windows hosts are out of scope.
Setup effort
Run a container — build the image from the repository, then point your client at it
USE CASES
Giving an agent your notes as working memory
It searches, follows wikilinks and reads the vault guide instead of being briefed from scratch each session.
Editing one section without touching the rest
Target a heading, preview the change as a unified diff, then apply it.
Reorganising the vault safely
Move a note and rewrite the links that pointed at it, with deletes going to trash rather than away.
Getting a file from your desk into the vault
Mint a single-use link bound to one destination path, open it, pick the file, and check that it landed.
25 tools
Listed in project docs. Your client may show a different list.
semantic_searchlist_notesget_recentget_tagsget_vault_guideread_notecreate_noteedit_notemove_notedelete_noteset_frontmatterread_filewrite_filelist_filesdelete_filerequest_uploadcheck_uploadrequest_downloadimport_from_urlget_backlinksget_linksget_neighborhoodfind_relatedfind_orphansBEFORE YOU INSTALL
What to know first
Tools you get
25 tools named in the project's documentation.
Requires
- Docker and Docker Compose
- A Linux host with kernel 5.6 or newer (5.8 for the file-transfer tools); macOS and Windows hosts are out of scope, so run the container on a Linux VM
- A PostgreSQL 16 instance reachable from the container, with pgvector 0.8.0 or newer
- A client that can reach a remote MCP server over HTTP
- An embedding backend for the server: Ollama running bge-m3 (the default), or an OpenAI API key set as OPENAI_API_KEY in the server's .env
INSTALLATION
Connect your client
Build the image from the repository, then point your client at it.
Server setup
git clone https://github.com/maxkuminov/obsidian-mcp.git
cd obsidian-mcp
cp .env.example .env
$EDITOR .envIn docker-compose.yml, point the /obsidian volume at your vault (- <YOUR_PATH_TO_VAULT>:/obsidian), and pick an embedding backend in .env: EMBEDDING_PROVIDER=openai with OPENAI_API_KEY, or Ollama running bge-m3
The bundled Caddy configuration fails closed on /admin, /api and /authorize; set your own basic-auth hash in it before starting it
make init
make db-init
make deployMint an API key in the control panel, then point your MCP client at https://obsidian-mcp.<YOUR_DOMAIN>/mcp with Auth: Bearer <YOUR_TOKEN>
MCP Clients
Claude Code
claude mcp add --transport http obsidian https://obsidian-mcp.<YOUR_DOMAIN>/mcp --header "Authorization: Bearer <YOUR_TOKEN>"OPENAI_API_KEY is a setting for the server, not for your client: it goes in the server's .env only if you choose EMBEDDING_PROVIDER=openai. The default embedding backend is Ollama running bge-m3, which needs no key.