Claude Desktop extension for Obsidian. Zero plugins required.
docs/user-story.html for vision and user journey.
We use two native interfaces that require no plugins:
Obsidian stores vault paths in obsidian.json:
| OS | Config Path |
|---|---|
| Linux | ~/.config/obsidian/obsidian.json |
| macOS | ~/Library/Application Support/obsidian/obsidian.json |
| Windows | %APPDATA%/obsidian/obsidian.json |
| Tool | Description | Method |
|---|---|---|
list_vaults |
List all discovered Obsidian vaults | Read obsidian.json |
list_notes |
List notes in a vault/folder | Filesystem glob |
read_note |
Read note content | Filesystem read |
write_note |
Create or update a note | Filesystem write |
search_notes |
Full-text search across vault | Filesystem grep |
open_in_obsidian |
Open note in Obsidian app | obsidian://open URI |
create_daily_note |
Open/create today's daily note | obsidian://daily URI |
| Component | Status | Notes |
|---|---|---|
| Vault Discovery | VALIDATED | Linux POC complete. See poc/vault_discovery.py |
| Note Read/Write | VALIDATED | Read + Write POC complete. See poc/notes_*.py |
| Search | VALIDATED | In-memory index chosen. See poc/notes_search.py |
| URI Handler | VALIDATED | Cross-platform URI opening via obsidian:// |
| MCP Server | VALIDATED | Full MCP server with all tools |
| PyPI Package | VALIDATED | Published as mcp-obsidian-vault on PyPI |
Validated on Linux (WSL2). See poc/vault_discovery.py.
| Finding | Details |
|---|---|
| JSON Structure Confirmed | {"vaults": {"<id>": {"path": "...", "ts": ..., "open": bool}}} |
| Vault ID | Opaque string (likely UUID or hash). Use path.name for display name. |
| "open" field | Optional. Defaults to false when missing. |
| Vault paths | Absolute paths. May or may not exist on filesystem. |
Edge cases handled:
FileNotFoundErrorjson.JSONDecodeErrorFalseCross-platform notes:
Validated on Linux (WSL2). See poc/notes_reader.py.
| Finding | Details |
|---|---|
| Glob pattern | **/*.md finds all notes recursively |
| Hidden files | Skip files/folders starting with . (e.g., .obsidian/) |
| Encoding | UTF-8 works for standard Obsidian notes |
| Performance | 1000 notes listed in ~0.085s, read in ~0.021s |
API Design:
list_notes(vault_path) → All notes recursivelylist_notes_in_folder(vault_path, folder) → Notes in specific folderget_note(vault_path, note_path) → Single note by pathget_folder_structure(vault_path) → Top-level folders and notesEdge cases handled:
FileNotFoundErrorFileNotFoundErrorrelative_pathValidated on Linux (WSL2). See poc/notes_writer.py.
| Finding | Details |
|---|---|
| Atomic writes | Write to temp file, then os.replace() for crash safety |
| Path validation | Resolve paths and check they stay within vault bounds |
| Auto-create dirs | Parent directories created automatically |
| Performance | 10MB file written in ~0.01s |
API Design:
create_note(vault, path, content) → Create new note (error if exists)update_note(vault, path, content) → Update existing noteappend_to_note(vault, path, content) → Append to notedelete_note(vault, path) → Delete noteSecurity:
../, absolute paths, etc.PathTraversalError raised for escape attemptsBenchmarked three approaches. See poc/search_comparison.py.
| Approach | Init Time | Storage | Avg Query | Best For |
|---|---|---|---|---|
| Grep (regex) | 0ms | None | ~88ms | Small vaults (<100 notes) |
| In-Memory Index | ~120ms | ~5MB RAM | ~2.6ms | Medium vaults, speed priority |
| SQLite FTS5 | ~120ms | ~650KB disk | ~2.9ms | Large vaults, persistence |
Decision: In-Memory Index
API Design:
VaultSearcher(vault_path) → Build index on initsearch(query, case_sensitive, max_results) → Full-text searchsearch_by_title(query) → Search note namesrefresh() → Rebuild index after changes