obsidian-remote-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. obsidian_remote_mcp-0.1.0/.env.example +20 -0
  2. obsidian_remote_mcp-0.1.0/.github/workflows/ci.yml +28 -0
  3. obsidian_remote_mcp-0.1.0/.github/workflows/release.yml +64 -0
  4. obsidian_remote_mcp-0.1.0/.gitignore +24 -0
  5. obsidian_remote_mcp-0.1.0/Dockerfile +11 -0
  6. obsidian_remote_mcp-0.1.0/FEATURES.md +254 -0
  7. obsidian_remote_mcp-0.1.0/LICENSE +21 -0
  8. obsidian_remote_mcp-0.1.0/PKG-INFO +246 -0
  9. obsidian_remote_mcp-0.1.0/README.md +229 -0
  10. obsidian_remote_mcp-0.1.0/docker-compose.yml +17 -0
  11. obsidian_remote_mcp-0.1.0/pyproject.toml +52 -0
  12. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/__init__.py +1 -0
  13. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/config.py +51 -0
  14. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/domain/__init__.py +0 -0
  15. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/domain/index.py +198 -0
  16. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/domain/models.py +47 -0
  17. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/domain/parser.py +132 -0
  18. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/server.py +715 -0
  19. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/storage/__init__.py +0 -0
  20. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/storage/filesystem.py +51 -0
  21. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/storage/locking.py +16 -0
  22. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/storage/watcher.py +96 -0
  23. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/__init__.py +0 -0
  24. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/attachments.py +94 -0
  25. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/canvas.py +193 -0
  26. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/folders.py +153 -0
  27. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/kanban.py +285 -0
  28. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/query.py +318 -0
  29. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/read.py +263 -0
  30. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/templates.py +94 -0
  31. obsidian_remote_mcp-0.1.0/src/obsidian_mcp/tools/write.py +384 -0
  32. obsidian_remote_mcp-0.1.0/tests/conftest.py +33 -0
  33. obsidian_remote_mcp-0.1.0/tests/fixtures/sample_vault/broken_yaml.md +6 -0
  34. obsidian_remote_mcp-0.1.0/tests/fixtures/sample_vault/headings.md +11 -0
  35. obsidian_remote_mcp-0.1.0/tests/fixtures/sample_vault/no_frontmatter.md +3 -0
  36. obsidian_remote_mcp-0.1.0/tests/fixtures/sample_vault/simple.md +8 -0
  37. obsidian_remote_mcp-0.1.0/tests/fixtures/sample_vault/with_blocks.md +17 -0
  38. obsidian_remote_mcp-0.1.0/tests/fixtures/sample_vault/with_callouts.md +23 -0
  39. obsidian_remote_mcp-0.1.0/tests/fixtures/sample_vault/with_tasks.md +17 -0
  40. obsidian_remote_mcp-0.1.0/tests/test_coverage_gaps.py +228 -0
  41. obsidian_remote_mcp-0.1.0/tests/test_index.py +68 -0
  42. obsidian_remote_mcp-0.1.0/tests/test_index_extended.py +103 -0
  43. obsidian_remote_mcp-0.1.0/tests/test_parser.py +67 -0
  44. obsidian_remote_mcp-0.1.0/tests/test_parser_extended.py +150 -0
  45. obsidian_remote_mcp-0.1.0/tests/test_phase10.py +222 -0
  46. obsidian_remote_mcp-0.1.0/tests/test_phase5.py +165 -0
  47. obsidian_remote_mcp-0.1.0/tests/test_phase6.py +213 -0
  48. obsidian_remote_mcp-0.1.0/tests/test_phase7.py +203 -0
  49. obsidian_remote_mcp-0.1.0/tests/test_phase8.py +210 -0
  50. obsidian_remote_mcp-0.1.0/tests/test_phase9.py +238 -0
  51. obsidian_remote_mcp-0.1.0/tests/test_storage.py +55 -0
  52. obsidian_remote_mcp-0.1.0/tests/test_tools_query.py +183 -0
  53. obsidian_remote_mcp-0.1.0/tests/test_tools_write.py +56 -0
  54. obsidian_remote_mcp-0.1.0/uv.lock +1450 -0
@@ -0,0 +1,20 @@
1
+ # Required: absolute path to your Obsidian vault
2
+ VAULT_PATH=/path/to/your/vault
3
+
4
+ # Set to true to disable all write operations
5
+ READ_ONLY=false
6
+
7
+ # Comma-separated relative paths where writes are allowed (empty = everywhere)
8
+ WRITE_PATHS=
9
+
10
+ # Comma-separated paths to exclude from listing/search
11
+ EXCLUDE_PATHS=private,.obsidian,.trash
12
+
13
+ # Transport mode: "stdio" (default, local use) or "sse" (remote/network use)
14
+ TRANSPORT=stdio
15
+
16
+ # API key for network transport (SSE). If set, all requests must include:
17
+ # Authorization: Bearer <your-key>
18
+ # Generate with: openssl rand -hex 32
19
+ # Not required for stdio transport.
20
+ API_KEY=
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ ci:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up uv
17
+ uses: astral-sh/setup-uv@v4
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Install dependencies
22
+ run: uv sync --dev
23
+
24
+ - name: Lint (ruff)
25
+ run: uv run ruff check src/ tests/
26
+
27
+ - name: Tests
28
+ run: uv run pytest tests/ -v --tb=short
@@ -0,0 +1,64 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+
7
+ jobs:
8
+ # ── PyPI ────────────────────────────────────────────────────────────────────
9
+ publish-pypi:
10
+ name: Publish to PyPI
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ id-token: write # required for trusted publishing
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: astral-sh/setup-uv@v4
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Build
23
+ run: uv build
24
+
25
+ - name: Publish
26
+ run: uv publish
27
+ env:
28
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
29
+
30
+ # ── Docker → GHCR ───────────────────────────────────────────────────────────
31
+ publish-docker:
32
+ name: Publish Docker image to GHCR
33
+ runs-on: ubuntu-latest
34
+ permissions:
35
+ contents: read
36
+ packages: write
37
+
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+
41
+ - name: Log in to GHCR
42
+ uses: docker/login-action@v3
43
+ with:
44
+ registry: ghcr.io
45
+ username: ${{ github.actor }}
46
+ password: ${{ secrets.GITHUB_TOKEN }}
47
+
48
+ - name: Extract metadata
49
+ id: meta
50
+ uses: docker/metadata-action@v5
51
+ with:
52
+ images: ghcr.io/ykoellmann/obsidian-mcp
53
+ tags: |
54
+ type=semver,pattern={{version}}
55
+ type=semver,pattern={{major}}.{{minor}}
56
+ type=raw,value=latest
57
+
58
+ - name: Build and push
59
+ uses: docker/build-push-action@v6
60
+ with:
61
+ context: .
62
+ push: true
63
+ tags: ${{ steps.meta.outputs.tags }}
64
+ labels: ${{ steps.meta.outputs.labels }}
@@ -0,0 +1,24 @@
1
+ # Python
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ *.pyo
6
+ dist/
7
+ *.egg-info/
8
+
9
+ # Caches
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+
14
+ # Environment
15
+ .env
16
+
17
+ # Local planning notes
18
+ plan.md
19
+
20
+ # Example vault (local only – jeder nutzt seinen eigenen Vault)
21
+ example-vault/
22
+
23
+ # Lock files for file operations (vault per-file locks)
24
+ *.md.lock
@@ -0,0 +1,11 @@
1
+ FROM python:3.12-slim
2
+
3
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
4
+
5
+ WORKDIR /app
6
+ COPY pyproject.toml uv.lock ./
7
+ COPY src/ src/
8
+
9
+ RUN uv pip install --system --no-cache .
10
+
11
+ ENTRYPOINT ["obsidian-mcp"]
@@ -0,0 +1,254 @@
1
+ # Feature-Roadmap – second-brain-mcp
2
+
3
+ Ziel: vollständige Obsidian-Unterstützung und darüber hinaus.
4
+
5
+ ---
6
+
7
+ ## Phase 2 – Enhanced Core (nächste Schritte)
8
+
9
+ ### 2.1 Search mit Snippets & Ranking
10
+ **Tool:** `search_notes_tool` erweitern
11
+
12
+ Aktuell: gibt nur Pfad + Tags zurück, kein Kontext.
13
+
14
+ ```python
15
+ # Neu: strukturierte Ergebnisse
16
+ {
17
+ "path": "Notizen/Python-Tipps.md",
18
+ "score": 0.92,
19
+ "snippets": [
20
+ {"line": 23, "text": "...`filelock` verhält sich anders wenn der **Lock-File** auf einem NFS-Mount..."}
21
+ ],
22
+ "tags": ["konzept/programmierung"]
23
+ }
24
+ ```
25
+
26
+ - Ranking: exakter Treffer > Wort-Anfang > Teilstring
27
+ - Snippets: ±2 Zeilen um den Treffer, Match bold-markiert
28
+ - Optional: `regex=True` für Regex-Suche
29
+ - Optional: `limit=20` für Ergebnis-Anzahl
30
+
31
+ ### 2.2 `move_note` – Umbenennen + Backlink-Rewriting
32
+ **Neues Tool**
33
+
34
+ ```python
35
+ @mcp.tool()
36
+ def move_note(from_path: str, to_path: str) -> dict:
37
+ # 1. Datei verschieben (atomar)
38
+ # 2. Alle .md-Dateien im Vault scannen
39
+ # 3. [[from_path]]-Wikilinks auf [[to_path]] umschreiben
40
+ # 4. Index aktualisieren
41
+ # Gibt zurück: {moved: ..., updated_links_in: [...]}
42
+ ```
43
+
44
+ ### 2.3 `get_orphans` – Notizen ohne Backlinks
45
+ **Neues Tool**
46
+
47
+ ```python
48
+ @mcp.tool()
49
+ def get_orphans(exclude_folders: list[str] = ["Journal", "Templates"]) -> list[str]:
50
+ # Notizen die von keiner anderen Notiz verlinkt werden
51
+ # Journal-Einträge standardmäßig ausgeschlossen
52
+ ```
53
+
54
+ ### 2.4 `get_broken_links` – tote Wikilinks finden
55
+ **Neues Tool**
56
+
57
+ ```python
58
+ @mcp.tool()
59
+ def get_broken_links() -> list[dict]:
60
+ # [{source: "Notizen/X.md", link: "[[NichtExistent]]", line: 12}]
61
+ # Prüft gegen: existierende Dateien + bekannte Aliases
62
+ ```
63
+
64
+ ### 2.5 `list_notes_with_meta` – Listing mit Metadaten
65
+ **`list_notes_tool` Erweiterung**
66
+
67
+ ```python
68
+ @mcp.tool()
69
+ def list_notes_tool(folder: str = "", include_meta: bool = False) -> list[str | dict]:
70
+ # include_meta=True: [{path, title, tags, status, created, mtime}]
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Phase 3 – Graph & Navigation
76
+
77
+ ### 3.1 `get_link_graph` – traversierbarer Link-Graph
78
+ **Neues Tool – das wichtigste dieser Phase**
79
+
80
+ ```python
81
+ @mcp.tool()
82
+ def get_link_graph(
83
+ root: str, # Startnotiz
84
+ depth: int = 2, # wie viele Ebenen ausgehend
85
+ direction: str = "both", # "outgoing" | "incoming" | "both"
86
+ include_tags: bool = False
87
+ ) -> dict:
88
+ # {
89
+ # "root": "Projekte/second-brain-mcp.md",
90
+ # "nodes": [{"path": ..., "title": ..., "tags": [...]}],
91
+ # "edges": [{"from": ..., "to": ..., "type": "wikilink"}]
92
+ # }
93
+ ```
94
+
95
+ Erlaubt: „Zeig mir alles was mit diesem Projekt zusammenhängt (2 Ebenen tief)".
96
+
97
+ ### 3.2 `get_tag_tree` – verschachtelter Tag-Baum
98
+ **Neues Tool**
99
+
100
+ ```python
101
+ @mcp.tool()
102
+ def get_tag_tree() -> dict:
103
+ # {
104
+ # "konzept": {
105
+ # "mcp": ["Notizen/MCP-Protokoll.md"],
106
+ # "ki": {
107
+ # "llm": ["Notizen/Claude.md"]
108
+ # }
109
+ # },
110
+ # "projekt": {
111
+ # "aktiv": ["Projekte/second-brain-mcp.md"]
112
+ # }
113
+ # }
114
+ ```
115
+
116
+ ### 3.3 `get_vault_stats` – Vault-Statistiken
117
+ **Neues Tool**
118
+
119
+ ```python
120
+ @mcp.tool()
121
+ def get_vault_stats() -> dict:
122
+ # {total_notes, total_links, total_tags, orphans_count,
123
+ # broken_links_count, most_linked: [...], most_linking: [...]}
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Phase 4 – Vollständige Obsidian-Feature-Unterstützung
129
+
130
+ ### 4.1 Block-Referenzen
131
+ **Parser + Index erweitern**
132
+
133
+ - Parsen von `^block-id`-Ankern in Notizen
134
+ - Auflösen von `[[Note^block-id]]`-Links
135
+ - Neues Tool: `read_block(path, block_id) -> str`
136
+ - Index: `block_index[note_path][block_id] = line_number`
137
+
138
+ ### 4.2 Aliases – transparente Auflösung
139
+ **Parser + Index erweitern**
140
+
141
+ - `aliases:` aus Frontmatter in Index aufnehmen
142
+ - `[[Alias]]` → automatisch zur echten Notiz auflösen
143
+ - `get_broken_links` berücksichtigt Aliases (kein false positive)
144
+ - Neues Tool: `resolve_alias(name) -> str | None`
145
+
146
+ ### 4.3 Embedded Notes – `![[Note]]` Transclusion
147
+ **Parser + neues Tool**
148
+
149
+ - `![[Note]]` und `![[Note#Section]]` parsen
150
+ - Neues Tool: `render_note(path) -> str` – löst alle Embeddings auf,
151
+ gibt vollständigen Text zurück (für vollständigen Kontext an Claude)
152
+
153
+ ### 4.4 Daily Notes
154
+ **Neues Tool**
155
+
156
+ ```python
157
+ @mcp.tool()
158
+ def get_daily_note(date: str = "today") -> dict:
159
+ # date: "today" | "yesterday" | "YYYY-MM-DD"
160
+ # Sucht in Journal/ nach YYYY-MM-DD.md
161
+ # Erstellt aus Template wenn nicht vorhanden
162
+
163
+ @mcp.tool()
164
+ def get_daily_notes_range(from_date: str, to_date: str) -> list[dict]:
165
+ # Alle Daily Notes im Zeitraum, mit Aufgaben-Summary
166
+ ```
167
+
168
+ ### 4.5 Task-Extraktion quer über den Vault
169
+ **Neues Tool**
170
+
171
+ ```python
172
+ @mcp.tool()
173
+ def get_tasks(
174
+ status: str = "open", # "open" | "done" | "all"
175
+ folder: str = "",
176
+ tag: str | None = None
177
+ ) -> list[dict]:
178
+ # [{text, done, source_path, line, due_date (aus [[due::YYYY-MM-DD]])}]
179
+ ```
180
+
181
+ ### 4.6 Callout-Parsing
182
+ **Parser erweitern**
183
+
184
+ - Callout-Typen erkennen: `[!NOTE]`, `[!WARNING]`, `[!TIP]`, `[!IMPORTANT]`, `[!QUESTION]`
185
+ - In `Note`-Dataclass aufnehmen: `callouts: list[Callout]`
186
+ - `read_note` gibt Callouts strukturiert zurück
187
+
188
+ ### 4.7 Canvas-Support (`.canvas`-Dateien)
189
+ **Neues Tool**
190
+
191
+ Obsidian Canvas = JSON-Format mit Nodes und Edges.
192
+
193
+ ```python
194
+ @mcp.tool()
195
+ def read_canvas(path: str) -> dict:
196
+ # {nodes: [{id, type, text/file, x, y}], edges: [{from, to, label}]}
197
+
198
+ @mcp.tool()
199
+ def list_canvases() -> list[str]:
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Phase 5 – Erweiterte Suche & Abfragen
205
+
206
+ ### 5.1 Dataview-ähnliche Abfragen
207
+ **Neues Tool**
208
+
209
+ ```python
210
+ @mcp.tool()
211
+ def query_notes(
212
+ tags: list[str] | None = None,
213
+ status: str | None = None,
214
+ frontmatter_filter: dict | None = None, # {"prioritaet": {"gte": 3}}
215
+ sort_by: str = "mtime",
216
+ limit: int = 50
217
+ ) -> list[dict]:
218
+ # Filtert Notizen nach Metadaten ohne Volltextsuche
219
+ ```
220
+
221
+ ### 5.2 Fuzzy- und Regex-Suche
222
+ **`search_notes_tool` erweitern**
223
+
224
+ - `mode: "exact" | "fuzzy" | "regex"`
225
+ - Fuzzy: Levenshtein-Distanz, für Tippfehler-Toleranz
226
+
227
+ ### 5.3 Template-Engine
228
+ **Neues Tool**
229
+
230
+ ```python
231
+ @mcp.tool()
232
+ def create_from_template(
233
+ template_path: str,
234
+ output_path: str,
235
+ variables: dict # {"title": "Mein Projekt", "date": "2026-07-23"}
236
+ ) -> dict:
237
+ # Ersetzt {{title}}, {{date}} etc. im Template
238
+ # Erstellt Zieldatei atomar
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Build-Order
244
+
245
+ ```
246
+ Phase 2: 2.4 get_broken_links → 2.3 get_orphans → 2.1 Search+Snippets → 2.2 move_note → 2.5 list_meta
247
+ Phase 3: 4.2 Aliases (braucht Index) → 3.1 get_link_graph → 3.2 get_tag_tree → 3.3 vault_stats
248
+ Phase 4: 4.1 Block-Refs → 4.3 Embeddings → 4.4 Daily Notes → 4.5 Tasks → 4.6 Callouts → 4.7 Canvas
249
+ Phase 5: 5.1 Query → 5.2 Fuzzy → 5.3 Templates
250
+ ```
251
+
252
+ Aliases (4.2) vor dem Link-Graph (3.1), weil der Graph Aliases für korrekte
253
+ Auflösung braucht. Block-Refs (4.1) vor Embeddings (4.3), weil Embeddings
254
+ Block-Refs auflösen müssen.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yannik Koellmann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: obsidian-remote-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Obsidian vaults — read, write, search, graph, canvas, kanban
5
+ Project-URL: Homepage, https://github.com/ykoellmann/obsidian-mcp
6
+ Project-URL: Repository, https://github.com/ykoellmann/obsidian-mcp
7
+ Project-URL: Issues, https://github.com/ykoellmann/obsidian-mcp/issues
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: fastmcp>=2.0
12
+ Requires-Dist: filelock>=3.13
13
+ Requires-Dist: python-frontmatter>=1.1
14
+ Requires-Dist: pyyaml>=6.0
15
+ Requires-Dist: watchdog>=4.0
16
+ Description-Content-Type: text/markdown
17
+
18
+ # obsidian-mcp
19
+
20
+ [![CI](https://github.com/ykoellmann/obsidian-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/ykoellmann/obsidian-mcp/actions/workflows/ci.yml)
21
+
22
+ An MCP (Model Context Protocol) server for [Obsidian](https://obsidian.md) vaults. Connects Claude (or any MCP client) directly to your vault — read, write, search, navigate links, and manage notes, canvases, and kanban boards.
23
+
24
+ ## Why obsidian-mcp?
25
+
26
+ The official Obsidian MCP plugin requires the Obsidian desktop app to be running and only works on the same machine. **obsidian-mcp is a standalone server** — no Obsidian app needed.
27
+
28
+ The intended setup is to host obsidian-mcp on a server or NAS where your vault is continuously synced (via [Syncthing](https://syncthing.net), [git](https://github.com/denolehov/obsidian-git), [rclone](https://rclone.org), or [Obsidian Sync](https://obsidian.md/sync)). Claude then connects to that server over the network, so:
29
+
30
+ - **Always up to date** — the server sees every change your Obsidian app writes, immediately
31
+ - **Access from anywhere** — connect from Claude Desktop, Claude Code, or any MCP client on any machine, without the vault being present locally
32
+ - **Multiple clients** — several Claude sessions can read the vault simultaneously; writes are serialized with per-file locking
33
+ - **No app dependency** — the server runs headless and starts automatically (systemd, Docker, etc.)
34
+
35
+ ```
36
+ [Obsidian app] ──sync──► [vault on server] ◄──MCP── [Claude on any machine]
37
+ (phone/laptop) (NAS / VPS) (Claude Desktop / Code)
38
+ ```
39
+
40
+ ## Features
41
+
42
+ - **Read & Search** — read notes, search full-text (exact/regex/fuzzy), render embedded transclusions, inspect note outlines
43
+ - **Write** — create/overwrite notes, patch sections, append content, update frontmatter, manage tags, move notes with automatic wikilink rewriting
44
+ - **Folders** — list, create, delete, rename folders; renaming rewrites path-based wikilinks vault-wide
45
+ - **Query & Graph** — backlinks, broken links, orphan detection, BFS link graph, vault stats, task collection across vault
46
+ - **Dataview-like queries** — filter notes by tags, status, frontmatter fields, or inline fields (`key:: value`)
47
+ - **Periodic Notes** — read/preview daily, weekly, monthly, quarterly, yearly journal notes from templates
48
+ - **Canvas** — read, create, and patch Obsidian Canvas (`.canvas`) files
49
+ - **Kanban** — read, create, and manipulate Obsidian Kanban boards (columns and cards)
50
+ - **Attachments** — list, read (text or base64), and add binary files
51
+ - **Templates** — render Obsidian templates with built-in (`{{date}}`, `{{title}}`, …) and custom variables
52
+ - **MCP Resources** — expose vault notes, stats, and tags as MCP resources for direct context injection
53
+
54
+ ## Installation
55
+
56
+ **Via uvx (no clone needed):**
57
+ ```bash
58
+ VAULT_PATH=/your/vault uvx obsidian-remote-mcp
59
+ ```
60
+
61
+ **Via Docker (no Python needed):**
62
+ ```bash
63
+ docker compose up -d # see docker-compose.yml
64
+ ```
65
+
66
+ **From source:**
67
+ ```bash
68
+ git clone https://github.com/ykoellmann/obsidian-mcp.git
69
+ cd obsidian-mcp
70
+ uv sync
71
+ uv run obsidian-remote-mcp
72
+ ```
73
+
74
+ ## Configuration
75
+
76
+ Copy `.env.example` to `.env` and set your vault path:
77
+
78
+ ```env
79
+ VAULT_PATH=/path/to/your/obsidian/vault
80
+ # Optional:
81
+ # READ_ONLY=true # prevent all writes
82
+ # WRITE_PATHS=Notes/,Inbox/ # restrict writes to specific folders
83
+ # TRANSPORT=stdio # stdio (default) or sse
84
+ ```
85
+
86
+ ## Usage with Claude Code
87
+
88
+ Add to your MCP config (e.g. `~/.claude/mcp.json`):
89
+
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "obsidian": {
94
+ "command": "uv",
95
+ "args": ["--directory", "/path/to/obsidian-mcp", "run", "obsidian-remote-mcp"],
96
+ "env": {
97
+ "VAULT_PATH": "/path/to/your/obsidian/vault"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Usage with Claude Desktop
105
+
106
+ Add to `claude_desktop_config.json`:
107
+
108
+ ```json
109
+ {
110
+ "mcpServers": {
111
+ "obsidian": {
112
+ "command": "uv",
113
+ "args": ["--directory", "/path/to/obsidian-mcp", "run", "obsidian-remote-mcp"],
114
+ "env": {
115
+ "VAULT_PATH": "/path/to/your/obsidian/vault"
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ## Docker
123
+
124
+ ```bash
125
+ # 1. Copy and edit the environment variables
126
+ cp .env.example .env
127
+
128
+ # 2. Set HOST_VAULT_PATH and API_KEY in .env, then:
129
+ docker compose up -d
130
+ ```
131
+
132
+ The `docker-compose.yml` pulls the pre-built image from GHCR — no cloning or building required. To build locally instead, swap `image:` for `build: .` in the compose file.
133
+
134
+ ## Remote Setup (Recommended)
135
+
136
+ Run obsidian-mcp on a server and connect to it remotely via SSE transport.
137
+
138
+ **1. Generate an API key:**
139
+ ```bash
140
+ openssl rand -hex 32
141
+ ```
142
+
143
+ **2. Configure the server** (`docker-compose.yml` or `.env`):
144
+ ```env
145
+ VAULT_PATH=/data/vault
146
+ TRANSPORT=sse
147
+ API_KEY=your-generated-key
148
+ ```
149
+
150
+ **3. Start:**
151
+ ```bash
152
+ docker compose up -d # or: uv run obsidian-remote-mcp
153
+ ```
154
+
155
+ **4. Connect from your MCP client** (anywhere on the network):
156
+ ```json
157
+ {
158
+ "mcpServers": {
159
+ "obsidian": {
160
+ "type": "sse",
161
+ "url": "https://your-server/sse",
162
+ "headers": {
163
+ "Authorization": "Bearer your-generated-key"
164
+ }
165
+ }
166
+ }
167
+ }
168
+ ```
169
+
170
+ > **Security:** Always put a TLS-terminating reverse proxy (e.g. [Caddy](https://caddyserver.com)) in front when exposing to the internet. `API_KEY` is not needed for stdio transport (local use only).
171
+
172
+ Keep the vault synced on the server with Syncthing, git+cron, rclone, or Obsidian Sync — obsidian-mcp picks up changes automatically via its file watcher.
173
+
174
+ ## Vault Conventions (Customization)
175
+
176
+ Create `_AI_INSTRUCTIONS.md` in your vault root to teach the AI how your specific vault is organized:
177
+
178
+ ```markdown
179
+ ## Structure
180
+ - `Notes/` — evergreen notes and concepts
181
+ - `Projects/` — active and archived projects (tag: #project/active, #project/done)
182
+ - `Journal/` — daily notes (YYYY-MM-DD.md)
183
+
184
+ ## Frontmatter Schema
185
+ - status: active | done | inbox
186
+ - tags: nested with / (e.g. #concept/programming)
187
+
188
+ ## Conventions
189
+ - Link by stem only, never by full path
190
+ - Every note needs a created: date in frontmatter
191
+ ```
192
+
193
+ The server loads this file at startup and sends it to the AI as system instructions. Without it, built-in generic Obsidian syntax guidance is used. The `_AI_INSTRUCTIONS.md` is the right place for everything vault-specific — folder layout, tag schema, naming conventions, and any workflow rules.
194
+
195
+ ## Tool Reference
196
+
197
+ | Category | Tools |
198
+ |---|---|
199
+ | **Read** | `list_notes`, `read_note`, `search_notes`, `render_note`, `get_note_outline` |
200
+ | **Write** | `write_note`, `patch_note`, `delete_note`, `append_to_note`, `patch_frontmatter`, `manage_tags`, `move_note` |
201
+ | **Folders** | `list_folder`, `create_folder`, `delete_folder`, `rename_folder` |
202
+ | **Query** | `query_notes`, `get_backlinks`, `get_broken_links`, `get_orphans`, `get_link_graph`, `get_vault_stats`, `get_tasks`, `resolve_alias` |
203
+ | **Tags** | `get_notes_by_tag`, `get_tag_tree`, `list_all_tags` |
204
+ | **Periodic** | `get_daily_note`, `get_periodic_note` |
205
+ | **Canvas** | `list_canvases`, `read_canvas`, `write_canvas`, `patch_canvas` |
206
+ | **Kanban** | `read_kanban`, `create_kanban_board`, `add_kanban_card`, `move_kanban_card`, `delete_kanban_card` |
207
+ | **Attachments** | `list_attachments`, `read_attachment`, `add_attachment` |
208
+ | **Templates** | `list_templates`, `create_from_template` |
209
+
210
+ Full parameter documentation is embedded in the server and shown automatically to connected AI clients.
211
+
212
+ ## Architecture
213
+
214
+ ```
215
+ src/obsidian_mcp/
216
+ ├── config.py # env-based config (VAULT_PATH, READ_ONLY, WRITE_PATHS)
217
+ ├── server.py # FastMCP entry point, tool and resource registrations
218
+ ├── domain/
219
+ │ ├── models.py # Note dataclass (frontmatter, tags, wikilinks, tasks, …)
220
+ │ ├── parser.py # Markdown parser (YAML frontmatter, wikilinks, block refs, …)
221
+ │ └── index.py # VaultIndex — alias resolution, backlinks, tag tree, BFS
222
+ ├── storage/
223
+ │ ├── filesystem.py # atomic writes (temp + os.replace), path validation
224
+ │ ├── locking.py # per-file filelock to prevent concurrent write conflicts
225
+ │ └── watcher.py # watchdog-based vault change detection (polling fallback)
226
+ └── tools/
227
+ ├── read.py # read_note, search_notes, render_note, get_note_outline
228
+ ├── write.py # write_note, patch_note, move_note, manage_tags, …
229
+ ├── query.py # graph tools, task aggregation, periodic notes, query_notes
230
+ ├── folders.py # folder management
231
+ ├── canvas.py # Obsidian Canvas (.canvas JSON) tools
232
+ ├── kanban.py # Obsidian Kanban plugin tools
233
+ ├── attachments.py # binary and text attachment handling
234
+ └── templates.py # template rendering with variable substitution
235
+ ```
236
+
237
+ ## Development
238
+
239
+ ```bash
240
+ uv run pytest # run tests (238 tests)
241
+ uv run ruff check src/ tests/ # lint
242
+ ```
243
+
244
+ ## License
245
+
246
+ MIT