Your Obsidian vault is useful only if your agent can find the right note.
Keyword search misses context. Full write-capable Obsidian MCP servers expose more power than a retrieval agent needs. Obsidian plugins are great inside Obsidian, but they are not always the right boundary for Codex, Claude Desktop, Cursor, or any other MCP client.
This project is the narrow version:
local Obsidian vault -> read-only scanner -> local SQLite index -> MCP search/read tools
Read-only semantic search MCP server for Obsidian vaults using local Ollama embeddings.
Add the server at user scope so the personal vault path stays out of project .mcp.json files:
claude mcp add obsidian_semantic --scope user \
--env OBSIDIAN_VAULT_ROOT="/path/to/your/Obsidian Vault" \
--env OBSIDIAN_SEMANTIC_MCP_HOME="/Users/you/.obsidian-semantic-mcp" \
--env OLLAMA_BASE_URL="http://localhost:11434" \
--env OBSIDIAN_EMBED_MODEL="bge-m3" \
--env OBSIDIAN_SEMANTIC_AUTO_INDEX="true" \
-- npx -y @dalecb/obsidian-semantic-mcp
claude mcp get obsidian_semantic
Start a new Claude Code session and run /mcp to confirm the server is connected. On native Windows, use -- cmd /c npx -y @dalecb/obsidian-semantic-mcp after the environment flags.
In Claude Desktop, open Settings → Developer → Edit Config. This opens ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows. Merge the following server into mcpServers:
Fully quit and restart Claude Desktop. Cursor and other JSON-style stdio MCP clients can use the same mcpServers entry. On Windows, if npx is not found, use "command": "cmd" and prepend "/c", "npx" to args.
This isn't aiming to be the most powerful Obsidian automation server. It aims to be the safest retrieval tool you can hand an agent.
Here's how it stacks up against the two tools it usually comes down to — a full-permission Obsidian MCP server (Local REST API based) and GBrain (a broader knowledge-compilation platform):
This project
Full-permission Obsidian MCP
GBrain
Access model
Read-only: search / read / index
Read + write + edit + delete
Read + write; compiles notes into its own model
Touches your vault
Never
Yes
Yes — restructures content
Obsidian must run
No — reads files directly
Yes — needs the REST API plugin
No
Required local runtime
Node.js + Ollama
Obsidian + plugin
Standalone platform
Embeddings & data
Local Ollama by default; remote URL is configurable
Local API; embeddings vary by setup
Built-in pipeline; optional sync
Storage
One SQLite file you can delete and rebuild
Plugin-managed
Its own store / migration
Best for
A small read-only retrieval boundary for agents
Full vault automation and editing
Building a compiled knowledge base across sources
That trade is on purpose: give up writing, editing, and running inside Obsidian, and you get fewer moving parts and a smaller blast radius in return.
Use this if your agent should answer:
"Which note explains this project decision?"
"Find the file where I wrote about idempotency payload mismatch."
"Show me the career notes related to this interview topic."
"Search my vault, but do not mutate it."
Do not use this if you want an Obsidian UI plugin, automatic note generation, or write-capable vault automation.
index_vault
-> scan Markdown files under OBSIDIAN_VAULT_ROOT
-> block denied paths and symlink escapes
-> split notes by Markdown headings
-> create one summary chunk per file
-> embed chunks with Ollama bge-m3
-> store notes, chunks, FTS rows, and vectors in SQLite
search_notes
-> incrementally index on first search or first search after a Markdown change
-> embed the query with Ollama
-> score vector similarity
-> score SQLite FTS5 keyword matches
-> apply title/path/heading metadata boosts
-> regroup chunk matches into file-level results
Default storage:
~/.obsidian-semantic-mcp/
data/semantic.sqlite
The vault remains the source of truth. The SQLite database is a derived index and can be deleted/rebuilt.
Safety Model
The server reads your vault and never writes to it. Three layers decide what an agent can see.
1. Always denied (system / tooling). Never indexed, no override:
.obsidian/, .smart-env/, .claude/, .codex-*/
any hidden folder (name starts with .)
node_modules, cache, logs
2. Sensitive — denied by default, unlockable. Stays blocked even when a tool call passes include_sensitive: true, unless the server is started with OBSIDIAN_SEMANTIC_ALLOW_SENSITIVE=true. Defaults to 08_PersonalInfo/. Override the list with OBSIDIAN_SEMANTIC_SENSITIVE_PATHS (comma- or newline-separated folders):
"Don't index this at all" → OBSIDIAN_SEMANTIC_EXCLUDE
"Keep it locked, but I can unlock it with a flag when I need to" → OBSIDIAN_SEMANTIC_SENSITIVE_PATHS + OBSIDIAN_SEMANTIC_ALLOW_SENSITIVE
Additional guards:
All paths are resolved through realpath.
Path traversal and URL-encoded traversal are blocked.
Symlinks that escape the vault root or point to a denied in-vault path are blocked.
search_notes and read_note enforce the current exclusion settings on every call. With automatic indexing enabled, the next search also removes excluded stale rows from the index. Confirm the active lists with index_status.
The local index stores snippets and embedding vectors. Treat it as a derived copy of your vault. See PRIVACY.md.
Indexing Strategy
The default is action-driven, not timer-based:
The first search_notes after server start.
The first search_notes after a Markdown create, update, delete, or rename event.
The first search_notes after the embedding model or index format changes; this triggers a full rebuild.
An explicit index_vault call.
The filesystem watcher only marks the index dirty. Indexing runs once, immediately before the next search, and concurrent searches share that work. If recursive watching is unavailable, the server falls back to an incremental scan before every search.
Disable automatic indexing with:
OBSIDIAN_SEMANTIC_AUTO_INDEX = "false"
Then call index_vault { "mode": "incremental" } yourself. The legacy OBSIDIAN_SEMANTIC_STARTUP_INDEX=true remains supported, but is not the default because it does work even when no search follows.
Search Quality Evaluation
Create a JSON file containing queries and expected paths to compare keyword, semantic, and hybrid modes under the same conditions: