obsidian-mcp-fast
README.md
# obsidian-mcp-fast
> Highly-performant MCP server for Obsidian vaults. **Sub-millisecond** retrieval. **No Obsidian app required.** Works with Claude Desktop, Claude Code, Cursor, and anything else that speaks MCP.
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
[](https://modelcontextprotocol.io/)
`obsidian-mcp-fast` is an [MCP](https://modelcontextprotocol.io/) server that exposes your Obsidian vault to any compatible LLM client (Claude Desktop, Claude Code, Cursor, ChatGPT custom MCPs, etc.) for **fast, structured retrieval**. It reads your `.md` files directly from disk — Obsidian doesn't need to be running. Pre-computed indexes live in memory. A file watcher keeps them fresh as you edit.
If you've used `MarkusPfundstein/mcp-obsidian` and hit slow searches, timeouts on larger vaults, or just want retrieval that's actually engineered for speed, this is for you. [Comparison below](#why-not-markuspfundsteinmcp-obsidian).
---
## What it does
Three retrieval primitives, exposed as **4 MCP tools**:
| Tool | What it does |
|---|---|
| `wiki_query` | **Primary tool.** Runs 3-layer hybrid retrieval (frontmatter filter → atomic-claim keyword search → wikilink graph expansion). Returns top cited claims, graph-adjacent context, suggested pages to read. |
| `wiki_read` | Fetch full content of a page by path or title (resolved via the graph). |
| `wiki_stats` | Vault overview: counts by type, top hubs, recent edits, watcher state. |
| `wiki_reindex` | Manually trigger reindex. Usually unnecessary — file watcher handles it. |
The LLM does the synthesis from what these tools return. The server does the retrieval.
---
## Why it's fast
| Move | Why it matters |
|---|---|
| **Long-running process, RAM-resident indexes** | Server stays alive between queries. No JSON parsing per call, no file I/O for layer 1/2/3. |
| **Direct file reads, no Obsidian REST API** | No HTTP round-trips, no localhost TLS handshake, no dependency on Obsidian being open. |
| **File watcher → debounced reindex** | Edit a note → 500ms later the indexes update. Most queries hit a warm cache. |
| **Indexer is pure Python stdlib** | Sub-second on 60–600 page vaults. No heavy NLP libraries to load. |
| **Coarse tool API** | One `wiki_query` call replaces 5–10 file-by-file tool calls. Token cost is dominated by Claude inference, not tool plumbing. |
| **Atomic-claim retrieval** | Returns specific cited facts, not whole pages. The LLM synthesis context stays small and precise. |
### Benchmarks (60 pages, 584 atomic claims, 373 wikilink edges)
| Operation | Latency |
|---|---|
| `wiki_query` end-to-end | **<1ms** |
| `wiki_read` (single page) | ~1ms |
| `wiki_stats` | <1ms |
| Full reindex | ~15ms |
| Server startup | ~500ms (one-time) |
These numbers should hold up to ~10K pages / ~50K claims with no architectural changes. Past that, swap brute-force claim search for vector ANN ([roadmap](#roadmap)).
---
## Schema requirements (opinionated — read this)
This server is **purpose-built** for vaults that follow a specific schema. If your vault doesn't follow these conventions, retrieval will work but yield less than it could. See [docs/schema.md](docs/schema.md) for the full rationale.
### Directory layout
```
your-vault/
├── Skills/ # Curated capability hubs (domain layer above types)
├── People/ # Real humans (founders, marketers, thinkers, etc.)
├── Brands/ # Companies and brand case studies
├── Frameworks/ # Actionable principles (have How To Apply + Anti-Patterns)
├── Concepts/ # Passive ideas, phenomena, definitions
├── References/ # Recurring resources (podcasts, dashboards, doc sites)
├── Sources/ # Immutable per-source notes (one per article/book/episode)
└── _System/
└── index/ # Generated retrieval indexes (auto-managed)
```
**Skills are first-class retrieval anchors.** A `Skill` page is a curated hub that aggregates many Frameworks/Concepts/People/Brands under a single domain (e.g. "Offer Construction" pulls together Grand Slam Offer + Value Equation + Niche Down + …). When an agent gets a domain-shaped question, it queries `wiki_query(type_filter=["skill"])` to find relevant hubs, then `wiki_read("Offer Construction")` to load the curated set in one call. This is faster and more coherent than searching individual frameworks one by one.
### Page schema
Every entity page has YAML frontmatter:
```markdown
---
type: framework # one of: person | brand | framework | concept | reference | source
created: 2026-05-04
updated: 2026-05-04
tags: [meta-ads, dtc] # for filtering
related: [Andromeda] # explicit cross-refs (also queryable)
---
# Page Title
## Key Facts
- Atomic claim 1 stated as a fact ([[Source - Article 2026]])
- Atomic claim 2 ([[Source - Other 2025]])
## How To Apply # required for Frameworks
1. Step
2. Step
## Examples
### [[Brand Name]]
- How it shows up here ([[Source - Article 2026]])
## Sources
- [[Source - Article 2026]]
```
Three disciplines drive the retrieval performance:
1. **Atomic claim-bullets** — one bullet, one fact, one inline `([[Source - X]])` cite. The indexer treats each as an atomic retrievable unit.
2. **Wikilinks for entity references** — `[[Page Name]]` syntax everywhere. Builds the wikilink graph for free.
3. **Typed frontmatter** — `type:`, `tags:`, `related:` — enables structured filtering (Layer 1) before any keyword search.
If you'd like a complete reference architecture for this schema (templates, ingest pipeline, linter, etc.), see [the-edge-wiki](https://github.com/thedavidquan01/the-edge-wiki) — the project this MCP was extracted from.
---
## Installation
### Prerequisites
- macOS, Linux, or Windows (WSL)
- Python 3.10+
- [uv](https://docs.astral.sh/uv/) (`brew install uv` on macOS, or see [installation guide](https://docs.astral.sh/uv/getting-started/installation/))
### Install via `uv tool` (recommended)
```bash
uv tool install obsidian-mcp-fast
```
This installs the `obsidian-mcp-fast` and `obsidian-mcp-fast-index` commands globally, isolated in their own venv.
### Install from source
```bash
git clone https://github.com/thedavidquan01/obsidian-mcp-fast
cd obsidian-mcp-fast
uv sync
```
Run via `uv run obsidian-mcp-fast` from the repo root.
### Build the indexes once
Before first use, build the indexes:
```bash
OBSIDIAN_VAULT_PATH=/path/to/your/vault obsidian-mcp-fast-index
# Indexed 60 pages, 584 atomic claims, 373 wikilink edges in 27ms.
```
The MCP server will also build them automatically on first startup if missing.
---
## Connect to a client
### Claude Desktop (macOS)
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-fast",
"env": {
"OBSIDIAN_VAULT_PATH": "/Users/you/Documents/your-vault"
}
}
}
}
```
If `obsidian-mcp-fast` isn't found, use the absolute path (`which obsidian-mcp-fast`).
Restart Claude Desktop. The 4 tools should appear in the tool picker.
### Claude Code (CLI)
Add to `.mcp.json` at your project root, or `~/.claude/mcp_settings.json` for user-wide:
```json
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-fast",
"env": {
"OBSIDIAN_VAULT_PATH": "/Users/you/Documents/your-vault"
}
}
}
}
```
Restart Claude Code (`/clear` or new session).
### Cursor
Settings → MCP → Add MCP Server → use the same JSON shape.
### Other clients
The server speaks standard MCP over stdio. Any client that supports stdio MCP works. See `examples/` for additional config snippets.
---
## Usage
Once connected, ask the LLM client wiki questions. The LLM will call `wiki_query` first, then optionally `wiki_read` on suggested pages, then synthesize:
> *"Use the obsidian tools. What does my vault say about earning a fresh Entity ID on Meta?"*
> *"Use obsidian to find all DTC growth frameworks tagged for paid social, then summarize the common threads."*
> *"Read [[Strategic Volume]] from my vault and explain how it relates to Andromeda."*
> *"What does my vault know about pricing strategy? Cite sources."*
For best results, mention the tool's connector name (whatever you set as the key in `mcpServers`) so Claude reliably routes to it.
---
## Tool reference
### `wiki_query(query, keywords?, type_filter?, tag_filter?, related_to?, max_claims?, max_pages?)`
3-layer hybrid retrieval. Returns:
```json
{
"query": "...",
"keywords_used": [...],
"filters_applied": { "type": [...], "tags": [...], "related_to": [...] },
"candidates_after_layer1": 13,
"top_claims": [
{ "page": "Concepts/Entity ID.md", "type": "concept", "line": 22, "text": "...", "sources": ["Source - X"], "wikilinks": ["Andromeda"] }
],
"graph_context": {
"Andromeda": { "type": "concept", "outbound": [...], "inbound": [...] }
},
"suggested_pages_to_read": ["Concepts/Entity ID.md", ...],
"stats": { "total_pages": 60, "candidate_pool": 13, "claims_searched": 584, "latency_ms": 0.51 }
}
```
**Parameters:**
- `query` *(required)* — natural-language question
- `keywords` — explicit search terms (3–5 variants for best recall). Derived from query if omitted.
- `type_filter` — restrict to types (e.g. `["framework", "concept"]`)
- `tag_filter` — required tags (any-of match)
- `related_to` — only pages whose `related[]` contains any of these titles
- `max_claims` — top-K claims (default 15)
- `max_pages` — suggested pages to read (default 8)
### `wiki_read(page)`
Returns:
```json
{
"path": "Concepts/Entity ID.md",
"title": "Entity ID",
"type": "concept",
"content": "---\ntype: concept\n...\n",
"line_count": 55
}
```
Accepts either a page path or title.
### `wiki_stats()`
Returns vault-wide stats: page counts by type, top hubs, recent edits, index file mtimes, watcher state.
### `wiki_reindex()`
Force a rebuild. Returns counts + latency.
---
## Configuration
| Env var | Default | Purpose |
|---|---|---|
| `OBSIDIAN_VAULT_PATH` | (required) | Path to vault root |
| `WIKI_ROOT` | (legacy alias) | Same as `OBSIDIAN_VAULT_PATH` |
Or pass `--vault-path` / `--index-dir` as CLI args.
---
## Why not `MarkusPfundstein/mcp-obsidian`?
That server is a thin wrapper around Obsidian's Local REST API plugin running inside the Obsidian app. Comparison:
| Concern | `MarkusPfundstein/mcp-obsidian` | `obsidian-mcp-fast` |
|---|---|---|
| **Requires Obsidian to be running** | Yes (with REST API plugin enabled) | No (reads files directly) |
| **Per-tool-call overhead** | HTTP localhost round-trip + TLS handshake (~5–50ms) | In-memory dict lookup (<1ms) |
| **Search timeout** | Hardcoded 6s — fails on vaults >9K notes ([#88](https://github.com/MarkusPfundstein/mcp-obsidian/issues/88)) | None — server holds state in RAM |
| **Caching** | None — every call re-fetches via HTTP | RAM-resident indexes, file watcher invalidates |
| **Frontmatter-aware retrieval** | No | Yes (Layer 1 filtering) |
| **Atomic claim retrieval** | No (file-level only) | Yes (one cited bullet = one retrievable unit) |
| **Wikilink graph traversal** | No | Yes (Layer 3 expansion) |
| **Read-only by default** | No (read+write+delete tools exposed → "lethal trifecta", [#121](https://github.com/MarkusPfundstein/mcp-obsidian/issues/121)) | Yes (writes intentionally not exposed) |
| **Maintenance status** | Last commit June 2025; 87 open issues; [#90](https://github.com/MarkusPfundstein/mcp-obsidian/issues/90) calls for new maintainers | Active |
| **UTF-8 handling on writes** | Broken ([#135, #93, #73](https://github.com/MarkusPfundstein/mcp-obsidian/issues/135)) | N/A (read-only) |
| **`OBSIDIAN_HOST/PORT` env vars** | Documented but ignored at runtime ([#86, #111, #112](https://github.com/MarkusPfundstein/mcp-obsidian/issues/111)) | N/A (no HTTP layer) |
If you need write access to your vault from an LLM, that server may still suit you. If you want fast, schema-aware retrieval, this one is built for that.
---
## Telemetry
Every tool call appends a JSON line to `<vault>/_System/index/queries.jsonl` with `tool`, `args`, `result` summary, `latency_ms`. Inspect with `jq`:
```bash
# 5 slowest queries
jq -s 'sort_by(-.latency_ms) | .[0:5]' queries.jsonl
# Tool usage frequency
jq -r '.tool' queries.jsonl | sort | uniq -c | sort -rn
```
Telemetry is local-only. Nothing leaves your machine.
---
## Troubleshooting
**"Could not resolve vault root"** — Set `OBSIDIAN_VAULT_PATH` env var, or run from inside a directory containing the expected dirs (`People/`, `Brands/`, `Frameworks/`, `Concepts/`, `References/`) or a `CLAUDE.md`.
**"Indexes missing at startup"** — The server tries to build them automatically. If that fails, run `obsidian-mcp-fast-index` manually first.
**Watcher not picking up edits** — On Linux, ensure `inotify` watch limit isn't exhausted: `cat /proc/sys/fs/inotify/max_user_watches`. Raise via `sudo sysctl fs.inotify.max_user_watches=524288`.
**Tools not appearing in Claude Desktop** — Check the MCP log: `~/Library/Logs/Claude/mcp*.log`. Common cause is path resolution; use absolute paths in the config.
**Want to verify it works without a client** — `OBSIDIAN_VAULT_PATH=/path/to/vault uv run python -c "from obsidian_mcp_fast import server; from pathlib import Path; server._startup(Path('/path/to/vault'), None); print(server.wiki_stats())"`
---
## Roadmap
- **Embeddings (Layer 2.5).** Add when keyword + Claude reformulation demonstrably misses queries (likely past ~2,000 atomic claims). `sentence-transformers/all-MiniLM-L6-v2` runs on CPU; numpy cosine sim under 50K claims; HNSW above.
- **Incremental reindex.** Currently the watcher triggers a full reindex (sub-second, fine at this scale). At 1000+ pages, parse only the changed file.
- **HTTP/SSE transport.** For Claude.ai web access. Will require the user to host the server somewhere reachable (Cloudflare Worker, fly.io, etc.).
- **Block-level retrieval.** Obsidian's `^block-id` references for sub-paragraph atomicity, alongside the current per-bullet claim model.
- **Generalized schema mode.** A pluggable conventions layer for vaults that don't follow the opinionated schema.
- **PyPI release.** Currently install via uv from GitHub; PyPI is the next packaging step.
---
## Contributing
Issues and PRs welcome. The codebase is small (~600 LOC across 5 files):
```
src/obsidian_mcp_fast/
├── server.py # MCP entry, tool registrations, in-memory state
├── retrieval.py # 3-layer hybrid logic (pure functions)
├── watcher.py # watchdog file events → debounced reindex
├── telemetry.py # per-call latency log
└── indexer.py # vault → 3 JSON indexes
```
For substantial changes, please open an issue first to discuss direction.
---
## License
[MIT](LICENSE)
## Acknowledgements
- The [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk).
- The schema discipline (atomic claims, wikilink graph, typed frontmatter) is adapted from Andrej Karpathy's [LLM Wiki](https://github.com/karpathy/...) pattern, refined for retrieval performance.
- Originally extracted from [the-edge-wiki](https://github.com/thedavidquan01/the-edge-wiki).
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues