A read-only semantic search layer for Obsidian vaults that keeps agents from mutating your notes. Uses local Ollama embeddings (default bge-m3) and stores a SQLite index outside your vault. Exposes four tools: index_status, index_vault (incremental or full rebuild), search_notes (hybrid semantic plus FTS5 keyword ranking), and read_note (by path or line range). Blocks .obsidian, personal folders, and symlink escapes by default. The index splits notes by heading and creates file-level summary chunks, then regroups search results into per-file matches with snippets and line ranges. Reach for this when your agent needs to find the right note by meaning without exposing write, rename, or delete operations.
Read-only semantic retrieval for agents that need to find the right Obsidian note without write access.
한국어 · Quick Start · Why This Exists · How It Works
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
No note writes. No cloud embeddings. No Obsidian plugin runtime. No sync service.
Status:
0.2.1early preview. The server is usable today, but ranking behavior and tool schemas may change before1.0.
| Need | What this server does |
|---|---|
| Find the note an agent should read | Hybrid semantic + keyword search over Markdown notes |
| Keep the vault safe | Exposes search/read/index/status only; no write, patch, move, rename, or delete tools |
| Stay local-first | Uses Ollama embeddings and stores the index on your machine |
| Make results agent-friendly | Returns file-level matches with headings, snippets, and line ranges |
| Avoid plugin state | Reads the vault directly from the filesystem; Obsidian does not need to be running |
Example result shape:
{
"path": "02_Projects/RealtimeAPI/05_Interview_QA.md",
"title": "Interview Q&A",
"score": 0.7431,
"matched_sections": [
{
"heading": "Level 4 > Redis Lua atomicity",
"lines": [266, 305],
"reason": "semantic=1, keyword=0.5565, metadata=0.6"
}
]
}
Requirements:
>= 24Install the embedding model:
ollama pull bge-m3
curl http://localhost:11434/api/tags
Print setup guidance:
npx -y --package @dalecb/obsidian-semantic-mcp obsidian-semantic-mcp-setup
Add this to ~/.codex/config.toml:
[mcp_servers.obsidian_semantic]
command = "npx"
args = ["-y", "@dalecb/obsidian-semantic-mcp"]
[mcp_servers.obsidian_semantic.env]
OBSIDIAN_VAULT_ROOT = "/path/to/your/Obsidian Vault"
OBSIDIAN_SEMANTIC_MCP_HOME = "/Users/you/.obsidian-semantic-mcp"
OBSIDIAN_EMBED_MODEL = "bge-m3"
OBSIDIAN_SEMANTIC_STARTUP_INDEX = "false"
Restart Codex, then run:
obsidian_semantic.index_status
obsidian_semantic.index_vault { "mode": "incremental" }
obsidian_semantic.search_notes { "query": "Redis Lua atomicity", "limit": 5 }
Claude Desktop, Cursor, and other JSON-style MCP clients can use:
{
"mcpServers": {
"obsidian_semantic": {
"command": "npx",
"args": ["-y", "@dalecb/obsidian-semantic-mcp"],
"env": {
"OBSIDIAN_VAULT_ROOT": "/path/to/your/Obsidian Vault",
"OBSIDIAN_SEMANTIC_MCP_HOME": "/Users/you/.obsidian-semantic-mcp",
"OBSIDIAN_EMBED_MODEL": "bge-m3",
"OBSIDIAN_SEMANTIC_STARTUP_INDEX": "false"
}
}
}
}
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 |
| Extra runtime | None | Obsidian + plugin | Standalone platform |
| Embeddings & data | Local Ollama; nothing leaves the machine | 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:
Do not use this if you want an Obsidian UI plugin, automatic note generation, or write-capable vault automation.
index_statusReturns index metadata and safety settings.
index_vaultBuilds or updates the external SQLite index.
{ "mode": "incremental" }
Specific files:
{
"mode": "incremental",
"paths": ["02_Projects/My Note.md"]
}
search_notesSearches notes with hybrid semantic and keyword ranking.
{
"query": "live coding notes",
"limit": 8,
"mode": "hybrid"
}
Modes:
hybrid: semantic vector + SQLite FTS5 + metadata boostssemantic: vector-first searchkeyword: FTS5 keyword search without embedding the queryread_noteReads a note or line range by vault-relative path.
{
"path": "02_Projects/My Note.md",
"start_line": 10,
"end_line": 40
}
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
-> 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
logs/
cache/
The vault remains the source of truth. The SQLite database is a derived index and can be deleted/rebuilt.
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-*/.)node_modules, cache, logs2. 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):
OBSIDIAN_SEMANTIC_SENSITIVE_PATHS = "08_PersonalInfo, 09_Finance"
3. Your own excludes — always denied. Folders you never want indexed, searched, or read. No unlock flag:
OBSIDIAN_SEMANTIC_EXCLUDE = "03_Journal, Private, Clients/Acme"
Which one do you want?
OBSIDIAN_SEMANTIC_EXCLUDEOBSIDIAN_SEMANTIC_SENSITIVE_PATHS + OBSIDIAN_SEMANTIC_ALLOW_SENSITIVEAdditional guards:
realpath.Changing these lists only affects new indexing. Already-indexed notes keep their stored state until you reindex. After tightening
EXCLUDEorSENSITIVE, runindex_vault { "mode": "full" }sosearch_notescannot surface stale hits. (read_notealways enforces the live config.) You can confirm the active lists withindex_status.
The local index stores snippets and embedding vectors. Treat it as a derived copy of your vault. See PRIVACY.md.
The server does not watch your vault in real time.
After editing notes, run:
{ "mode": "incremental" }
This keeps the first public version predictable and avoids background watcher risks. You can opt into startup indexing with:
OBSIDIAN_SEMANTIC_STARTUP_INDEX = "true"
npm test
npm run pack:check
Before publishing:
npm pack --dry-run
Confirm the package does not include data/, *.sqlite, or private vault files.
MIT
OBSIDIAN_VAULT_ROOT*Absolute path to the local Obsidian vault.
OBSIDIAN_SEMANTIC_MCP_HOMEDirectory for the external SQLite index, logs, and cache.
OBSIDIAN_EMBED_MODELOllama embedding model name. Defaults to bge-m3.
OLLAMA_BASE_URLOllama API base URL. Defaults to http://localhost:11434.
OBSIDIAN_SEMANTIC_STARTUP_INDEXSet to true to run incremental indexing when the server starts.
OBSIDIAN_SEMANTIC_ALLOW_SENSITIVESet to true to allow include_sensitive tool calls.
OBSIDIAN_SEMANTIC_EXCLUDEComma- or newline-separated folders to always exclude from indexing, search, and read.
OBSIDIAN_SEMANTIC_SENSITIVE_PATHSComma- or newline-separated folders treated as sensitive (blocked unless unlocked). Defaults to 08_PersonalInfo/.
io.github.ericm1018/skillfm-llm-cost-optimizer-openai-anthropic-usage
io.github.mikerawsonnz/llm-orchestration-agent
io.github.mikerawsonnz/authenticated-llm-agent
labforgedev/copilot-memory-mcp
csoai-org/agent-prompt-injection-firewall-mcp
io.github.mikerawsonnz/authenticated-multi-llm-agent