Install
$ agentstack add mcp-maxkuminov-obsidian-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Obsidian MCP Server
[](https://www.python.org/) [](LICENSE) [](https://modelcontextprotocol.io) [](https://www.postgresql.org/)
A self-hosted Model Context Protocol server that turns your Obsidian vault into shared memory between you and your AI agents. Indexed, searchable, and self-describing — agents read what you read, link what you link, and pick up your folder layout, frontmatter schema, and tag conventions on the first call instead of being briefed from scratch every session.
Stack: Python 3.12, FastAPI, PostgreSQL with pgvector. Pluggable embeddings (Ollama bge-m3, or OpenAI text-embedding-3-{small,large}).
Contents
- [Why this exists](#why-this-exists)
- [A session at the keyboard](#a-session-at-the-keyboard)
- [What's in the box](#whats-in-the-box)
- [vs. other Obsidian MCP servers](#vs-other-obsidian-mcp-servers)
- [Who this is for](#who-this-is-for)
- [Control panel](#control-panel)
- [Quick start](#quick-start)
- [Cost expectations](#cost-expectations)
- [The self-describing vault](#the-self-describing-vault)
- [Multi-user mode](#multi-user-mode)
- [Configuration](#configuration)
- [Architecture](#architecture)
- [Project layout](#project-layout)
- [Development](#development)
- [Security notes](#security-notes)
Why this exists
There are three things going on here, and they're more interesting together than apart.
1. A shared memory layer between you and your agents
I think of my Obsidian vault as my exocortex. The "big me" that includes notes, calendars, scripts, search, and AI assistants is substantially more capable than the "small me" of the biological brain alone. It's also where I do most of my thinking, because writing something down is itself a form of thought.
The problem is that until recently, the vault was passive. I had to go find things. Agents that wanted to help me had to be briefed from scratch every session, and they had no way to see what I'd already written about a topic.
This server fixes that. Now the same vault feeds my own daily writing and any agent I plug into it. The agent reads what I read, links what I link, follows the same wikilinks, sees the same frontmatter. When I write a project note on Sunday, my Monday-morning briefing agent already knows about it. When the agent leaves notes from a research session, they show up in my normal Obsidian search.
A concrete version of this: I'll spend a session in Claude Code on a project, wrap up, push the commits, and then just say "update Obsidian." The agent reads the vault guide, figures out where project notes live in my structure, picks the right format and frontmatter, and leaves a session log I can later roll into a status report. No path-passing, no telling it what to write — the conventions are already in the vault, and it follows them.
That's the exocortex idea made concrete: one place that holds context, and both the human and the agents reading and writing into it on the same terms.
2. Agent memory that you can actually read
The other half is the inverse. If you let an agent run for a while, it needs memory. Most setups solve this with an opaque vector store, a SQLite blob, or a managed "memory" service that you can't see into. That works until you want to know what the agent thinks it knows about you, or you need to correct something, or you want to understand why it just made a weird suggestion.
This server gives you a different deal. Agent memory lives as markdown files in your vault. Folder structure, file names, frontmatter, all visible. You can open the file in Obsidian and read it. You can edit it. You can delete it. You can grep it. The agent's "memory" is a human-auditable artifact that sits in the same place as your own notes, with the same tools available.
The home lab is the use case that sold me on this. My vault has notes on the rack, the network, and every Home Assistant integration. I can say "set up a night-light mode in the master bathroom, 1% after 11pm" and a sysadmin agent finds the right config, makes the change, and updates the doc in the same pass. Six months later when I've forgotten how it works, the answer is in the vault, not buried in some chat history I can't search.
The semantic search and wikilink graph still work over that material, so retrieval is fast and conceptual. But the substrate is files you own, not a black box.
3. The vault follows you
The thing that still surprises me is that this is internet-facing. Same vault, same notes, reachable from anywhere I happen to be talking to an agent — Claude in a browser tab, Claude on my phone on the train, an n8n workflow firing on a schedule, a Claude Code session on whatever laptop is in front of me. All behind OAuth or a bearer key, all hitting the same source of truth.
So when I hear an interesting podcast on a commute and want to think out loud about it with Claude on my phone, the agent isn't starting cold. It can pull up what I've already written on adjacent topics, surface a note I half-finished six months ago, and at the end of the conversation suggest updates and write them in. The vault doesn't have to be near me to be the thing I'm working in.
A session at the keyboard
To make the abstract concrete, a short transcript from a real session. Tool calls inline; the user types in plain English and the agent picks the right tool.
you > find anything I've written about why retrieval-augmented
chat is hard
claude
→ semantic_search(query="why retrieval-augmented chat is hard")
• 2025-12-03 - RAG failure modes.md
• 2025-10-18 - Chunking tradeoffs.md
• 2024-06-22 - Vector recall vs precision.md
the closest hit is your December note on RAG failure modes. let
me see what it's connected to.
→ get_neighborhood(path="2025-12-03 - RAG failure modes.md",
depth=1)
8 connected notes. notable ones:
• "Hybrid search" — a re-ranking design you sketched but
didn't ship
• "Out-of-distribution queries" — a half-finished draft from
October
• "Embedding model choice" — links to bge-m3, your current
model
you have three connected pieces here: a failure-modes catalog,
an unshipped hybrid-search sketch, and an abandoned O-O-D draft.
want me to summarize all three together, or pull up the draft
so you can decide whether to finish it?
you > pull up the draft, and tag it #revisit-2026 in frontmatter
while you're there
claude
→ read_note(path="2025-10-18 - OOD queries (draft).md")
→ set_frontmatter(
path="2025-10-18 - OOD queries (draft).md",
updates={"tags": ["draft", "rag", "revisit-2026"]}
)
here's the draft, and the tag is on. you stopped right before
the section on confidence thresholds; the open question you
left yourself was…
Two things to notice. First, the agent didn't need to be told what folder to look in or what tools to use — it picked them. Second, the write at the end is structured (set_frontmatter mutating YAML, not a regex over the file body), so the note round-trips cleanly. The self-describing vault and the wikilink graph are doing the work that makes this feel natural.
What's in the box
The server exposes 20 MCP tools across six concerns.
Search and discovery
keyword_search(query, folder?, tags?, frontmatter?, limit=20),
full-text via PostgreSQL tsvector; the text-search config(s) are configurable via FTS_CONFIGS (see [Full-text search language(s)](#full-text-search-languages))
semantic_search(query, folder?, tags?, frontmatter?, limit=15),
vector similarity via pgvector, one preview chunk per note
list_notes(folder?, limit=50), sorted by modified timeget_recent(folder?, limit=20), recently changedget_tags(limit=50), tag and countget_vault_guide(), the Obsidian primer plus this vault's
CLAUDE.md, served live
Read and write
read_note(path)create_note(path, content), atomic write, refuses overwriteedit_note(path, …)with four mutually exclusive modes: full
replace (default), append=True, find=… (with optional replace_all), or section= (ATX headings, supports Parent/Child path-style disambiguation). dry_run=True returns a unified diff without writing.
move_note(from_path, to_path, rewrite_links=False), relocates and
optionally rewrites incoming [[Old]], [[Old|alias]], [[Old#anchor]], ![[Old]], and [[folder/Old]] references in source notes
delete_note(path, permanent=False), soft-delete to
.trash/- by default. permanent=True does a hard os.unlink.
set_frontmatter(path, updates, remove?), structured YAML
mutation. Body is byte-identical when only frontmatter changes.
File access (non-markdown)
Raw read/write/browse of arbitrary vault files (PDFs, images, skill assets, data files) — distinct peers to the note tools, which stay markdown-only. Pure byte transport: no server-side PDF/text extraction, no embedding or indexing of non-markdown files.
read_file(path, encoding="auto"), returns text-like files as text,
images as an inline image block that renders in-client, and other binaries as a base64 string. text/base64 force the form. Refuses files over MAX_FILE_READ_BYTES (default 10 MB).
write_file(path, content, encoding="base64", overwrite=False),
lands a file in the vault; base64 for binary, text for UTF-8. No-clobber by default, auto-creates parent dirs, atomic write. Capped at MAX_FILE_WRITE_BYTES (default 25 MB).
list_files(folder=".", pattern="*", recursive=False, limit=200),
ls-style browse of files and subdirectories with size and mtime, glob-filterable and result-capped.
All three reuse the path-traversal guard and exclude dot-directories (.obsidian, .git, .trash, …), matching the indexer's visibility rule.
Wikilink graph
get_backlinks(path, limit=50), notes linking TOpathget_links(path), outgoing links, both resolved and danglingget_neighborhood(path, depth=1, limit=50), undirected BFS over the
resolved-link graph, capped at depth ≤ 5 and limit ≤ 200
find_related(path, limit=10), semantic neighbors via averaged
chunk embeddings and pgvector cosine distance, deduped per note
find_orphans(folder?, limit=50), notes with zero in or out
resolved links
Auth and ops
- API keys with the
omcp_prefix, stored as SHA-256 hashes, with
read and readwrite permission scopes. Write tools refuse on read-only keys.
- OAuth 2.0 PKCE (S256) flow for clients like Claude Desktop and
claude.ai.
- Control panel (Jinja2, htmx, Tailwind) for keys, usage logs,
indexer status, embedding-provider info, and a danger-zone reset.
- Every tool call is logged to
usage_logswith name, params
(truncated to 200 chars), duration, and response size.
All write tools route through src/services/vault.py::write_file, which writes to a tmp file in the same directory and os.replace()s it onto the destination. A crash mid-write cannot truncate a note.
vs. other Obsidian MCP servers
There are several existing MCP servers for Obsidian, and most of them solve a different problem than this one. The lightweight ones are glue over Obsidian's Local REST API plugin or the filesystem: they let an agent reach the files, but don't build any infrastructure of their own. They're great if "I just want Claude to read my notes" is the goal and you keep Obsidian running locally.
This server is on the other end of the spectrum: a real backend with a persistent index, semantic retrieval, a wikilink graph, OAuth, and an admin UI. The cost is Postgres and Docker. The benefit is everything you can build on top of that.
| | This server | [MarkusPfundstein/mcp-obsidian][mp] | [StevenStavrakis/obsidian-mcp][sg] | [jacksteamdev/obsidian-mcp-tools][js] | | --- | --- | --- | --- | --- | | Persistent index (Postgres) | ✅ | — | — | — | | Semantic search (vectors) | ✅ | — | — | — | | Wikilink graph queries | ✅ | — | — | partial | | Runs without Obsidian open | ✅ | — | ✅ | — | | OAuth 2.0 client flow | ✅ | — | — | — | | Multi-user / per-user vaults | ✅ | — | — | — | | Admin UI + usage logs | ✅ | — | — | — | | Atomic writes + dry-run diffs | ✅ | — | — | — | | Setup tax | Postgres + Docker | Obsidian + REST plugin | Python only | Obsidian plugin |
[mp]: https://github.com/MarkusPfundstein/mcp-obsidian [sg]: https://github.com/StevenStavrakis/obsidian-mcp [js]: https://github.com/jacksteamdev/obsidian-mcp-tools
Comparison reflects each project's documented features at time of writing; verify the specifics before betting on them.
Who this is for
- Homelab folks who already run Postgres and Docker, or are happy
to spin them up. The setup tax is the price of admission for the semantic and graph layers.
- People who keep an opinionated vault — task placement logic,
frontmatter schemas, tag taxonomy — and want agents to follow those conventions on the first call instead of being briefed every session.
- Anyone running more than one MCP client (Claude Desktop, Claude
Code, Claude in a browser, n8n) against the same notes and tired of re-explaining the vault to each.
- Folks who want agent memory to live as plain markdown files they
can read, edit, grep, and version-control, not in an opaque vector store or a managed memory service.
Who this isn't for
- "I just want Claude to read my notes" with the lightest possible
setup. Use one of the filesystem-glue projects above; you don't need this.
- Anyone unwilling to run a database. There is no SQLite fallback;
pgvector is doing real work, and a managed Postgres with pgvector support is part of the stack.
- People who want a turnkey hosted product. This is a self-hosted
server you run yourself.
Control panel
The server ships with a built-in admin UI for the parts of operations that are easier to look at than to query: minting keys, watching the indexer, eyeballing tool-call traffic, and resetting embeddings when you switch providers.
Usage
Per-tool-call audit log with a 14-day request histogram. Every MCP call is recorded with the calling key, tool name, duration, and response size — useful for noticing a misbehaving agent burning tokens on something it shouldn't.
API keys and OAuth clients
Bearer keys with read / readwrite scopes for API clients, and a separate OAuth 2.0 PKCE flow for clients like Claude Desktop and claude.ai that expect a proper authorization-code dance.
Vault browser
A read-only file tree of the mounted vault, mostly for sanity-checking that the container sees what you think it sees.
Settings
Indexer status, current embedding provider and model, vault path, and the danger-zone reset that drops and recreates the embeddings column at the configured dimension. Use this when switching providers.
Quick start
> Deploying on a VPS from scratch? See [DEPLOYMENT.md](./DEPLOYMENT.md) > for the full walkthrough: Postgres setup, Caddy and TLS, vault sync > via Nextcloud, and the gotchas that bite first-time deploys.
Prerequisites
- Docker and Docker Compose
- A PostgreSQL 16 instance reachable from the container, with the
pgvector extension installed
- Either an Ollama instance running
bge-m3, or an OpenAI API key.
Anything that speaks the OpenAI embeddings protocol works (Azure OpenAI, OpenRouter, Together, etc.).
1. Clone, configure, point at your vault
git clone https://github.com/maxkuminov/obsidian-mcp.git
cd obsidian-mcp
cp .env.example .env
$EDITOR .env
In docker-compose.yml, point the /obsidian volume at your vault:
volumes:
- /path/to/your/vault:/obsidian
2. Pick an embedding backend
Option A, OpenAI (zero local infra):
EMBEDDING_PROVIDER=openai
OPENAI_API_KEY=sk-...
EMBEDDING_DIMENSIONS=1024
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
The server validates OPENAI_API_KEY at startup and refuses to boot if it's missing.
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: maxkuminov
- Source: maxkuminov/obsidian-mcp
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.