mcp-obsidian-cli 1.0.0 → 1.1.0
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.
- package/README.md +17 -0
- package/package.json +5 -1
- package/prompts/obsidian-bases.md +380 -0
- package/prompts/obsidian-canvas.md +312 -0
- package/prompts/obsidian-cli.md +115 -0
- package/prompts/obsidian-markdown.md +224 -0
- package/server.js +101 -18
package/README.md
CHANGED
|
@@ -69,6 +69,23 @@ The generic `obsidian` tool means the MCP server never falls behind the CLI —
|
|
|
69
69
|
| `OBSIDIAN_VAULT` | _(none)_ | Target vault by name |
|
|
70
70
|
| `OBSIDIAN_CLI_PATH` | `obsidian` | Path to CLI binary |
|
|
71
71
|
| `OBSIDIAN_TIMEOUT_MS` | `15000` | Command timeout |
|
|
72
|
+
| `XDG_CONFIG_HOME` | `~/.config` | Base path for config file |
|
|
73
|
+
|
|
74
|
+
## Config file
|
|
75
|
+
|
|
76
|
+
The server can read settings from a YAML config file:
|
|
77
|
+
|
|
78
|
+
- Default: `~/.config/mcp-obsidian-cli/config.yaml`
|
|
79
|
+
- With `XDG_CONFIG_HOME`: `$XDG_CONFIG_HOME/mcp-obsidian-cli/config.yaml`
|
|
80
|
+
|
|
81
|
+
Config file format:
|
|
82
|
+
```yaml
|
|
83
|
+
vault: "my-vault"
|
|
84
|
+
cliPath: "obsidian"
|
|
85
|
+
timeoutMs: 15000
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Config precedence: env vars > config file > hardcoded defaults
|
|
72
89
|
|
|
73
90
|
## Compared to alternatives
|
|
74
91
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-obsidian-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server wrapping the Obsidian CLI — full native API access over Model Context Protocol",
|
|
6
6
|
"main": "server.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"mcp-obsidian-cli": "server.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"server.js",
|
|
12
|
+
"prompts/"
|
|
13
|
+
],
|
|
10
14
|
"scripts": {
|
|
11
15
|
"start": "node server.js",
|
|
12
16
|
"test": "node --test test/run.test.js"
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Obsidian Bases Reference
|
|
2
|
+
|
|
3
|
+
Bases are database-like views of vault notes, defined as `.base` YAML files. They let you query, filter, and display notes as tables, card galleries, lists, or maps — without writing custom code.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
- **File format:** `.base` files containing valid YAML
|
|
8
|
+
- **How they work:** Obsidian reads all vault notes, applies your filters, computes any formulas, and renders results in the configured views
|
|
9
|
+
- **When to use:** Tracking tasks, reading lists, project dashboards, meeting logs, any situation where you want a live view across multiple notes
|
|
10
|
+
|
|
11
|
+
## YAML Schema
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
# Global filters apply to ALL views
|
|
15
|
+
filters:
|
|
16
|
+
and: []
|
|
17
|
+
or: []
|
|
18
|
+
not: []
|
|
19
|
+
|
|
20
|
+
# Computed properties available across all views
|
|
21
|
+
formulas:
|
|
22
|
+
formula_name: 'expression'
|
|
23
|
+
|
|
24
|
+
# Display name overrides for properties
|
|
25
|
+
properties:
|
|
26
|
+
property_name:
|
|
27
|
+
displayName: "Display Name"
|
|
28
|
+
formula.formula_name:
|
|
29
|
+
displayName: "Formula Display Name"
|
|
30
|
+
file.ext:
|
|
31
|
+
displayName: "Extension"
|
|
32
|
+
|
|
33
|
+
# Custom summary formulas
|
|
34
|
+
summaries:
|
|
35
|
+
custom_name: 'values.mean().round(3)'
|
|
36
|
+
|
|
37
|
+
# One or more views
|
|
38
|
+
views:
|
|
39
|
+
- type: table | cards | list | map
|
|
40
|
+
name: "View Name"
|
|
41
|
+
limit: 10
|
|
42
|
+
groupBy:
|
|
43
|
+
property: property_name
|
|
44
|
+
direction: ASC | DESC
|
|
45
|
+
filters:
|
|
46
|
+
and: []
|
|
47
|
+
order:
|
|
48
|
+
- file.name
|
|
49
|
+
- property_name
|
|
50
|
+
- formula.formula_name
|
|
51
|
+
summaries:
|
|
52
|
+
property_name: Average
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Filters
|
|
56
|
+
|
|
57
|
+
Filters narrow which notes appear. Apply globally (to all views) or per-view.
|
|
58
|
+
|
|
59
|
+
### Filter Structure
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
# Single filter expression
|
|
63
|
+
filters: 'status == "done"'
|
|
64
|
+
|
|
65
|
+
# AND - all conditions must be true
|
|
66
|
+
filters:
|
|
67
|
+
and:
|
|
68
|
+
- 'status == "done"'
|
|
69
|
+
- 'priority > 3'
|
|
70
|
+
|
|
71
|
+
# OR - any condition can be true
|
|
72
|
+
filters:
|
|
73
|
+
or:
|
|
74
|
+
- 'file.hasTag("book")'
|
|
75
|
+
- 'file.hasTag("article")'
|
|
76
|
+
|
|
77
|
+
# NOT - exclude matching items
|
|
78
|
+
filters:
|
|
79
|
+
not:
|
|
80
|
+
- 'file.hasTag("archived")'
|
|
81
|
+
|
|
82
|
+
# Nested filters
|
|
83
|
+
filters:
|
|
84
|
+
or:
|
|
85
|
+
- file.hasTag("tag")
|
|
86
|
+
- and:
|
|
87
|
+
- file.hasTag("book")
|
|
88
|
+
- file.hasLink("Textbook")
|
|
89
|
+
- not:
|
|
90
|
+
- file.hasTag("book")
|
|
91
|
+
- file.inFolder("Required Reading")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Filter Operators
|
|
95
|
+
|
|
96
|
+
| Operator | Description |
|
|
97
|
+
|----------|-------------|
|
|
98
|
+
| `==` | equals |
|
|
99
|
+
| `!=` | not equal |
|
|
100
|
+
| `>` | greater than |
|
|
101
|
+
| `<` | less than |
|
|
102
|
+
| `>=` | greater than or equal |
|
|
103
|
+
| `<=` | less than or equal |
|
|
104
|
+
| `&&` | logical and |
|
|
105
|
+
| `\|\|` | logical or |
|
|
106
|
+
| `!` | logical not |
|
|
107
|
+
|
|
108
|
+
Useful filter functions: `file.hasTag("tag")`, `file.inFolder("path/")`, `file.hasLink("Note Name")`.
|
|
109
|
+
|
|
110
|
+
## Properties
|
|
111
|
+
|
|
112
|
+
### Three Property Types
|
|
113
|
+
|
|
114
|
+
1. **Note properties** — from note frontmatter: `status`, `priority`, `due`
|
|
115
|
+
2. **File properties** — computed file metadata: `file.name`, `file.path`, `file.mtime`, etc.
|
|
116
|
+
3. **Formula properties** — your computed values: `formula.days_until_due`
|
|
117
|
+
|
|
118
|
+
### File Properties Reference
|
|
119
|
+
|
|
120
|
+
| Property | Type | Description |
|
|
121
|
+
|----------|------|-------------|
|
|
122
|
+
| `file.name` | String | File name with extension |
|
|
123
|
+
| `file.basename` | String | File name without extension |
|
|
124
|
+
| `file.path` | String | Full path from vault root |
|
|
125
|
+
| `file.folder` | String | Parent folder path |
|
|
126
|
+
| `file.ext` | String | File extension |
|
|
127
|
+
| `file.size` | Number | File size in bytes |
|
|
128
|
+
| `file.ctime` | Date | Created time |
|
|
129
|
+
| `file.mtime` | Date | Modified time |
|
|
130
|
+
| `file.tags` | List | All tags in file |
|
|
131
|
+
| `file.links` | List | Internal links in file |
|
|
132
|
+
| `file.backlinks` | List | Files linking to this file |
|
|
133
|
+
|
|
134
|
+
## Formula Syntax
|
|
135
|
+
|
|
136
|
+
Formulas compute values from properties. Define them in the `formulas` section, reference them as `formula.name` in views.
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
formulas:
|
|
140
|
+
# Simple arithmetic
|
|
141
|
+
total: "price * quantity"
|
|
142
|
+
|
|
143
|
+
# Conditional logic
|
|
144
|
+
status_icon: 'if(done, "Done", "Pending")'
|
|
145
|
+
|
|
146
|
+
# String formatting
|
|
147
|
+
formatted_price: 'if(price, price.toFixed(2) + " USD", "")'
|
|
148
|
+
|
|
149
|
+
# Date formatting
|
|
150
|
+
created_label: 'file.ctime.format("YYYY-MM-DD")'
|
|
151
|
+
|
|
152
|
+
# Days since creation
|
|
153
|
+
days_old: '(now() - file.ctime).days'
|
|
154
|
+
|
|
155
|
+
# Days until due date (with null guard)
|
|
156
|
+
days_until_due: 'if(due_date, (date(due_date) - today()).days, "")'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Key Functions
|
|
160
|
+
|
|
161
|
+
| Function | Description |
|
|
162
|
+
|----------|-------------|
|
|
163
|
+
| `date(string)` | Parse string to date (`YYYY-MM-DD`) |
|
|
164
|
+
| `now()` | Current date and time |
|
|
165
|
+
| `today()` | Current date (time = 00:00:00) |
|
|
166
|
+
| `if(condition, trueVal, falseVal?)` | Conditional expression |
|
|
167
|
+
| `duration(string)` | Parse duration string |
|
|
168
|
+
| `file(path)` | Get file object |
|
|
169
|
+
| `link(path, display?)` | Create a link |
|
|
170
|
+
|
|
171
|
+
### Duration Type
|
|
172
|
+
|
|
173
|
+
Subtracting two dates returns a **Duration** — not a number. Access a numeric field before applying math:
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
# CORRECT
|
|
177
|
+
"(now() - file.ctime).days" # Returns days as number
|
|
178
|
+
"(date(due) - today()).days.round(0)" # Rounded days
|
|
179
|
+
|
|
180
|
+
# WRONG — Duration doesn't support direct round()
|
|
181
|
+
# "(date(due) - today()).round(0)"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Date Arithmetic
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
"now() + \"1 day\"" # Tomorrow
|
|
188
|
+
"today() + \"7d\"" # A week from today
|
|
189
|
+
"now() - file.ctime" # Returns Duration type
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## View Types
|
|
193
|
+
|
|
194
|
+
### Table View
|
|
195
|
+
|
|
196
|
+
Displays notes as rows with sortable columns.
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
views:
|
|
200
|
+
- type: table
|
|
201
|
+
name: "Task List"
|
|
202
|
+
order:
|
|
203
|
+
- file.name
|
|
204
|
+
- status
|
|
205
|
+
- due_date
|
|
206
|
+
summaries:
|
|
207
|
+
priority: Average
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Cards View
|
|
211
|
+
|
|
212
|
+
Displays notes as a visual card gallery.
|
|
213
|
+
|
|
214
|
+
```yaml
|
|
215
|
+
views:
|
|
216
|
+
- type: cards
|
|
217
|
+
name: "Gallery"
|
|
218
|
+
order:
|
|
219
|
+
- file.name
|
|
220
|
+
- cover_image
|
|
221
|
+
- description
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### List View
|
|
225
|
+
|
|
226
|
+
Displays a minimal list of notes.
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
views:
|
|
230
|
+
- type: list
|
|
231
|
+
name: "Quick List"
|
|
232
|
+
order:
|
|
233
|
+
- file.name
|
|
234
|
+
- status
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Map View
|
|
238
|
+
|
|
239
|
+
Requires latitude/longitude properties and the Maps community plugin.
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
views:
|
|
243
|
+
- type: map
|
|
244
|
+
name: "Locations"
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Summaries
|
|
248
|
+
|
|
249
|
+
Summarize numeric or date columns in table footers.
|
|
250
|
+
|
|
251
|
+
| Summary | Input | Description |
|
|
252
|
+
|---------|-------|-------------|
|
|
253
|
+
| `Average` | Number | Mean value |
|
|
254
|
+
| `Sum` | Number | Total |
|
|
255
|
+
| `Min` | Number | Smallest value |
|
|
256
|
+
| `Max` | Number | Largest value |
|
|
257
|
+
| `Range` | Number | Max minus Min |
|
|
258
|
+
| `Median` | Number | Median value |
|
|
259
|
+
| `Stddev` | Number | Standard deviation |
|
|
260
|
+
| `Earliest` | Date | Earliest date |
|
|
261
|
+
| `Latest` | Date | Latest date |
|
|
262
|
+
| `Checked` | Boolean | Count of true |
|
|
263
|
+
| `Unchecked` | Boolean | Count of false |
|
|
264
|
+
| `Empty` | Any | Count of empty |
|
|
265
|
+
| `Filled` | Any | Count of non-empty |
|
|
266
|
+
| `Unique` | Any | Count of distinct values |
|
|
267
|
+
|
|
268
|
+
## Complete Examples
|
|
269
|
+
|
|
270
|
+
### Task Tracker
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
filters:
|
|
274
|
+
and:
|
|
275
|
+
- file.hasTag("task")
|
|
276
|
+
|
|
277
|
+
formulas:
|
|
278
|
+
days_until_due: 'if(due, (date(due) - today()).days, "")'
|
|
279
|
+
is_overdue: 'if(due, date(due) < today() && status != "done", false)'
|
|
280
|
+
priority_label: 'if(priority == 1, "High", if(priority == 2, "Medium", "Low"))'
|
|
281
|
+
|
|
282
|
+
properties:
|
|
283
|
+
formula.days_until_due:
|
|
284
|
+
displayName: "Days Until Due"
|
|
285
|
+
formula.priority_label:
|
|
286
|
+
displayName: Priority
|
|
287
|
+
|
|
288
|
+
views:
|
|
289
|
+
- type: table
|
|
290
|
+
name: "Active Tasks"
|
|
291
|
+
filters:
|
|
292
|
+
and:
|
|
293
|
+
- 'status != "done"'
|
|
294
|
+
order:
|
|
295
|
+
- file.name
|
|
296
|
+
- status
|
|
297
|
+
- formula.priority_label
|
|
298
|
+
- due
|
|
299
|
+
- formula.days_until_due
|
|
300
|
+
groupBy:
|
|
301
|
+
property: status
|
|
302
|
+
direction: ASC
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Reading List
|
|
306
|
+
|
|
307
|
+
```yaml
|
|
308
|
+
filters:
|
|
309
|
+
or:
|
|
310
|
+
- file.hasTag("book")
|
|
311
|
+
- file.hasTag("article")
|
|
312
|
+
|
|
313
|
+
formulas:
|
|
314
|
+
status_icon: 'if(status == "reading", "Reading", if(status == "done", "Done", "To Read"))'
|
|
315
|
+
year_read: 'if(finished_date, date(finished_date).year, "")'
|
|
316
|
+
|
|
317
|
+
views:
|
|
318
|
+
- type: cards
|
|
319
|
+
name: "Library"
|
|
320
|
+
order:
|
|
321
|
+
- cover
|
|
322
|
+
- file.name
|
|
323
|
+
- author
|
|
324
|
+
- formula.status_icon
|
|
325
|
+
filters:
|
|
326
|
+
not:
|
|
327
|
+
- 'status == "dropped"'
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Daily Notes Index
|
|
331
|
+
|
|
332
|
+
```yaml
|
|
333
|
+
filters:
|
|
334
|
+
and:
|
|
335
|
+
- file.inFolder("Daily Notes")
|
|
336
|
+
|
|
337
|
+
formulas:
|
|
338
|
+
word_estimate: '(file.size / 5).round(0)'
|
|
339
|
+
day_of_week: 'date(file.basename).format("dddd")'
|
|
340
|
+
|
|
341
|
+
properties:
|
|
342
|
+
formula.day_of_week:
|
|
343
|
+
displayName: "Day"
|
|
344
|
+
formula.word_estimate:
|
|
345
|
+
displayName: "~Words"
|
|
346
|
+
|
|
347
|
+
views:
|
|
348
|
+
- type: table
|
|
349
|
+
name: "Recent Notes"
|
|
350
|
+
limit: 30
|
|
351
|
+
order:
|
|
352
|
+
- file.name
|
|
353
|
+
- formula.day_of_week
|
|
354
|
+
- formula.word_estimate
|
|
355
|
+
- file.mtime
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## YAML Quoting Rules
|
|
359
|
+
|
|
360
|
+
- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
|
|
361
|
+
- Use double quotes for simple string values: `"My View Name"`
|
|
362
|
+
- Strings containing `:`, `{`, `}`, `[`, `]`, `#` must be quoted
|
|
363
|
+
|
|
364
|
+
## Embedding Bases in Notes
|
|
365
|
+
|
|
366
|
+
```markdown
|
|
367
|
+
![[MyBase.base]]
|
|
368
|
+
|
|
369
|
+
![[MyBase.base#View Name]]
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Using This Knowledge with MCP Tools
|
|
373
|
+
|
|
374
|
+
When working with Bases through this MCP server:
|
|
375
|
+
|
|
376
|
+
- Create a new base: `obsidian_create({ name: "Tasks", path: "bases/Tasks.base", content: "<yaml content>" })`
|
|
377
|
+
- Read an existing base definition: `obsidian_read({ path: "bases/Tasks.base" })`
|
|
378
|
+
- List all base files: `obsidian_files({ ext: "base" })`
|
|
379
|
+
- Set a property on a note that a base queries: `obsidian_property_set({ name: "status", value: "done", file: "My Task Note" })`
|
|
380
|
+
- Search for notes that match base criteria: `obsidian_search({ query: "tag:#task" })`
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# JSON Canvas Reference
|
|
2
|
+
|
|
3
|
+
Canvas files (`.canvas`) are JSON files that define visual layouts of notes, text, links, and groups on an infinite canvas. Use them for brainstorming, relationship mapping, project planning, and content organization.
|
|
4
|
+
|
|
5
|
+
## File Structure
|
|
6
|
+
|
|
7
|
+
A canvas file contains two top-level arrays following the JSON Canvas Spec 1.0:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"nodes": [],
|
|
12
|
+
"edges": []
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `nodes` — array of node objects placed on the canvas
|
|
17
|
+
- `edges` — array of connections between nodes
|
|
18
|
+
|
|
19
|
+
Both arrays are optional (empty canvas is valid).
|
|
20
|
+
|
|
21
|
+
## Common Workflows
|
|
22
|
+
|
|
23
|
+
### Create a New Canvas
|
|
24
|
+
|
|
25
|
+
1. Create a `.canvas` file with base structure `{"nodes": [], "edges": []}`
|
|
26
|
+
2. Generate unique 16-character hex IDs for each node (e.g., `"6f0ad84f44ce9c17"`)
|
|
27
|
+
3. Add nodes with required fields: `id`, `type`, `x`, `y`, `width`, `height`
|
|
28
|
+
4. Add edges referencing valid node IDs via `fromNode` and `toNode`
|
|
29
|
+
5. Validate: ensure all `fromNode`/`toNode` values reference existing node IDs
|
|
30
|
+
|
|
31
|
+
### Add a Node to an Existing Canvas
|
|
32
|
+
|
|
33
|
+
1. Read and parse the existing `.canvas` file
|
|
34
|
+
2. Generate a unique ID that does not collide with existing IDs
|
|
35
|
+
3. Choose `x`, `y` position that avoids overlapping existing nodes (50-100px spacing)
|
|
36
|
+
4. Append the new node to the `nodes` array
|
|
37
|
+
5. Optionally add edges connecting it to existing nodes
|
|
38
|
+
|
|
39
|
+
### Connect Two Nodes
|
|
40
|
+
|
|
41
|
+
1. Identify source and target node IDs
|
|
42
|
+
2. Generate a unique edge ID
|
|
43
|
+
3. Set `fromNode` and `toNode`; optionally set `fromSide`/`toSide` for anchor points
|
|
44
|
+
4. Optionally add a `label`
|
|
45
|
+
5. Append to the `edges` array
|
|
46
|
+
|
|
47
|
+
## Node Types
|
|
48
|
+
|
|
49
|
+
All nodes share these required attributes:
|
|
50
|
+
|
|
51
|
+
| Attribute | Required | Type | Description |
|
|
52
|
+
|-----------|----------|------|-------------|
|
|
53
|
+
| `id` | Yes | string | Unique 16-char hex identifier |
|
|
54
|
+
| `type` | Yes | string | `text`, `file`, `link`, or `group` |
|
|
55
|
+
| `x` | Yes | integer | X position in pixels (left edge) |
|
|
56
|
+
| `y` | Yes | integer | Y position in pixels (top edge) |
|
|
57
|
+
| `width` | Yes | integer | Width in pixels |
|
|
58
|
+
| `height` | Yes | integer | Height in pixels |
|
|
59
|
+
| `color` | No | string | Preset `"1"`-`"6"` or hex `"#RRGGBB"` |
|
|
60
|
+
|
|
61
|
+
### Text Nodes
|
|
62
|
+
|
|
63
|
+
Display rich text with Markdown content.
|
|
64
|
+
|
|
65
|
+
| Attribute | Required | Description |
|
|
66
|
+
|-----------|----------|-------------|
|
|
67
|
+
| `text` | Yes | Markdown string |
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"id": "6f0ad84f44ce9c17",
|
|
72
|
+
"type": "text",
|
|
73
|
+
"x": 0,
|
|
74
|
+
"y": 0,
|
|
75
|
+
"width": 400,
|
|
76
|
+
"height": 200,
|
|
77
|
+
"text": "# Hello World\n\nThis is **Markdown** content."
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use `\n` for line breaks in JSON strings. Do NOT use literal `\\n`.
|
|
82
|
+
|
|
83
|
+
### File Nodes
|
|
84
|
+
|
|
85
|
+
Display a vault note or attachment inline.
|
|
86
|
+
|
|
87
|
+
| Attribute | Required | Description |
|
|
88
|
+
|-----------|----------|-------------|
|
|
89
|
+
| `file` | Yes | Path to file from vault root |
|
|
90
|
+
| `subpath` | No | Link to heading or block (starts with `#`) |
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"id": "a1b2c3d4e5f67890",
|
|
95
|
+
"type": "file",
|
|
96
|
+
"x": 500,
|
|
97
|
+
"y": 0,
|
|
98
|
+
"width": 400,
|
|
99
|
+
"height": 300,
|
|
100
|
+
"file": "Projects/Alpha.md"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Link Nodes
|
|
105
|
+
|
|
106
|
+
Display an external URL as a web preview.
|
|
107
|
+
|
|
108
|
+
| Attribute | Required | Description |
|
|
109
|
+
|-----------|----------|-------------|
|
|
110
|
+
| `url` | Yes | External URL |
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"id": "c3d4e5f678901234",
|
|
115
|
+
"type": "link",
|
|
116
|
+
"x": 1000,
|
|
117
|
+
"y": 0,
|
|
118
|
+
"width": 400,
|
|
119
|
+
"height": 200,
|
|
120
|
+
"url": "https://obsidian.md"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Group Nodes
|
|
125
|
+
|
|
126
|
+
Visual containers for organizing other nodes. Position child nodes inside the group's bounds.
|
|
127
|
+
|
|
128
|
+
| Attribute | Required | Description |
|
|
129
|
+
|-----------|----------|-------------|
|
|
130
|
+
| `label` | No | Text label for the group |
|
|
131
|
+
| `background` | No | Path to background image |
|
|
132
|
+
| `backgroundStyle` | No | `cover`, `ratio`, or `repeat` |
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"id": "d4e5f6789012345a",
|
|
137
|
+
"type": "group",
|
|
138
|
+
"x": -50,
|
|
139
|
+
"y": -50,
|
|
140
|
+
"width": 1000,
|
|
141
|
+
"height": 600,
|
|
142
|
+
"label": "Project Overview",
|
|
143
|
+
"color": "4"
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Groups do not automatically contain nodes — nodes are "inside" a group visually based on position overlap.
|
|
148
|
+
|
|
149
|
+
## Edges
|
|
150
|
+
|
|
151
|
+
Edges connect nodes via their IDs.
|
|
152
|
+
|
|
153
|
+
| Attribute | Required | Default | Description |
|
|
154
|
+
|-----------|----------|---------|-------------|
|
|
155
|
+
| `id` | Yes | - | Unique identifier |
|
|
156
|
+
| `fromNode` | Yes | - | Source node ID |
|
|
157
|
+
| `toNode` | Yes | - | Target node ID |
|
|
158
|
+
| `fromSide` | No | - | `top`, `right`, `bottom`, or `left` |
|
|
159
|
+
| `toSide` | No | - | `top`, `right`, `bottom`, or `left` |
|
|
160
|
+
| `fromEnd` | No | `none` | `none` or `arrow` |
|
|
161
|
+
| `toEnd` | No | `arrow` | `none` or `arrow` |
|
|
162
|
+
| `color` | No | - | Line color (preset or hex) |
|
|
163
|
+
| `label` | No | - | Text label on the edge |
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"id": "0123456789abcdef",
|
|
168
|
+
"fromNode": "6f0ad84f44ce9c17",
|
|
169
|
+
"fromSide": "right",
|
|
170
|
+
"toNode": "a1b2c3d4e5f67890",
|
|
171
|
+
"toSide": "left",
|
|
172
|
+
"toEnd": "arrow",
|
|
173
|
+
"label": "leads to"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Color Presets
|
|
178
|
+
|
|
179
|
+
| Preset | Color |
|
|
180
|
+
|--------|-------|
|
|
181
|
+
| `"1"` | Red |
|
|
182
|
+
| `"2"` | Orange |
|
|
183
|
+
| `"3"` | Yellow |
|
|
184
|
+
| `"4"` | Green |
|
|
185
|
+
| `"5"` | Cyan |
|
|
186
|
+
| `"6"` | Purple |
|
|
187
|
+
|
|
188
|
+
Also accepts hex strings: `"#FF0000"`. Exact shades depend on the Obsidian theme.
|
|
189
|
+
|
|
190
|
+
## ID Generation
|
|
191
|
+
|
|
192
|
+
Use 16-character lowercase hexadecimal strings (64-bit random value):
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
"6f0ad84f44ce9c17"
|
|
196
|
+
"a3b2c1d0e9f8a7b6"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
IDs must be unique across all nodes and edges in the file.
|
|
200
|
+
|
|
201
|
+
## Layout Guidelines
|
|
202
|
+
|
|
203
|
+
- Coordinates can be negative — the canvas is infinite
|
|
204
|
+
- `x` increases rightward, `y` increases downward
|
|
205
|
+
- `x`, `y` mark the top-left corner of the node
|
|
206
|
+
- Space nodes 50-100px apart; leave 20-50px padding inside groups
|
|
207
|
+
- Align to grid (multiples of 10 or 20) for cleaner layouts
|
|
208
|
+
- Array order determines z-index: first node = bottom layer, last = top layer
|
|
209
|
+
|
|
210
|
+
| Node Type | Suggested Width | Suggested Height |
|
|
211
|
+
|-----------|-----------------|------------------|
|
|
212
|
+
| Small text | 200-300 | 80-150 |
|
|
213
|
+
| Medium text | 300-450 | 150-300 |
|
|
214
|
+
| Large text | 400-600 | 300-500 |
|
|
215
|
+
| File preview | 300-500 | 200-400 |
|
|
216
|
+
| Link preview | 250-400 | 100-200 |
|
|
217
|
+
|
|
218
|
+
## Validation Checklist
|
|
219
|
+
|
|
220
|
+
Before writing a canvas file, verify:
|
|
221
|
+
|
|
222
|
+
1. All `id` values are unique across nodes and edges
|
|
223
|
+
2. Every `fromNode` and `toNode` references an existing node ID
|
|
224
|
+
3. Required fields are present for each node type:
|
|
225
|
+
- text nodes: `text`
|
|
226
|
+
- file nodes: `file`
|
|
227
|
+
- link nodes: `url`
|
|
228
|
+
4. `type` is one of: `text`, `file`, `link`, `group`
|
|
229
|
+
5. `fromSide`/`toSide` are one of: `top`, `right`, `bottom`, `left`
|
|
230
|
+
6. `fromEnd`/`toEnd` are one of: `none`, `arrow`
|
|
231
|
+
7. Color presets are `"1"` through `"6"` or valid hex `"#RRGGBB"`
|
|
232
|
+
8. JSON is valid and parseable (especially: `\n` not `\\n` in text)
|
|
233
|
+
|
|
234
|
+
## Complete Example
|
|
235
|
+
|
|
236
|
+
A minimal project overview canvas with a group, two notes, a text summary, and a connecting edge:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"nodes": [
|
|
241
|
+
{
|
|
242
|
+
"id": "d4e5f6789012345a",
|
|
243
|
+
"type": "group",
|
|
244
|
+
"x": -60,
|
|
245
|
+
"y": -60,
|
|
246
|
+
"width": 1100,
|
|
247
|
+
"height": 500,
|
|
248
|
+
"label": "Project Alpha",
|
|
249
|
+
"color": "4"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"id": "6f0ad84f44ce9c17",
|
|
253
|
+
"type": "text",
|
|
254
|
+
"x": 0,
|
|
255
|
+
"y": 0,
|
|
256
|
+
"width": 350,
|
|
257
|
+
"height": 150,
|
|
258
|
+
"text": "## Goal\n\nDeliver MVP by end of quarter.\n\n- Backend API\n- Frontend UI\n- Documentation"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"id": "a1b2c3d4e5f67890",
|
|
262
|
+
"type": "file",
|
|
263
|
+
"x": 450,
|
|
264
|
+
"y": 0,
|
|
265
|
+
"width": 400,
|
|
266
|
+
"height": 300,
|
|
267
|
+
"file": "Projects/Alpha/Requirements.md"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"id": "b2c3d4e5f6789012",
|
|
271
|
+
"type": "link",
|
|
272
|
+
"x": 450,
|
|
273
|
+
"y": 320,
|
|
274
|
+
"width": 400,
|
|
275
|
+
"height": 120,
|
|
276
|
+
"url": "https://github.com/example/project-alpha"
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
"edges": [
|
|
280
|
+
{
|
|
281
|
+
"id": "0123456789abcdef",
|
|
282
|
+
"fromNode": "6f0ad84f44ce9c17",
|
|
283
|
+
"fromSide": "right",
|
|
284
|
+
"toNode": "a1b2c3d4e5f67890",
|
|
285
|
+
"toSide": "left",
|
|
286
|
+
"toEnd": "arrow",
|
|
287
|
+
"label": "defines"
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Using This Knowledge with MCP Tools
|
|
294
|
+
|
|
295
|
+
When working with canvas files through this MCP server:
|
|
296
|
+
|
|
297
|
+
- Create a new canvas:
|
|
298
|
+
```
|
|
299
|
+
obsidian_create({
|
|
300
|
+
name: "Project Map",
|
|
301
|
+
path: "canvas/Project Map.canvas",
|
|
302
|
+
content: "{\"nodes\": [], \"edges\": []}"
|
|
303
|
+
})
|
|
304
|
+
```
|
|
305
|
+
- Read an existing canvas (parse the JSON to see its structure):
|
|
306
|
+
`obsidian_read({ path: "canvas/Project Map.canvas" })`
|
|
307
|
+
- List all canvas files in the vault:
|
|
308
|
+
`obsidian_files({ ext: "canvas" })`
|
|
309
|
+
- Update a canvas (read, modify JSON, write back):
|
|
310
|
+
`obsidian_create({ path: "canvas/Project Map.canvas", content: "<updated json>" })`
|
|
311
|
+
- Search for notes referenced in canvases:
|
|
312
|
+
`obsidian_search({ query: "canvas" })`
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Obsidian CLI Reference
|
|
2
|
+
|
|
3
|
+
Use the `obsidian` CLI to interact with a running Obsidian instance. Obsidian must be open.
|
|
4
|
+
|
|
5
|
+
## Command Syntax
|
|
6
|
+
|
|
7
|
+
Commands follow this pattern:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
obsidian <command> [key=value ...]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Parameters** take a value with `=`. Quote values that contain spaces:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
obsidian create name="My Note" content="Hello world"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Flags** are boolean switches with no value:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
obsidian create name="My Note" silent overwrite
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For multiline content, use `\n` for newline and `\t` for tab.
|
|
26
|
+
|
|
27
|
+
## File Targeting
|
|
28
|
+
|
|
29
|
+
Many commands accept `file` or `path` to target a note. Without either, the active file is used.
|
|
30
|
+
|
|
31
|
+
- `file=<name>` — resolves like a wikilink (name only, no path or extension needed)
|
|
32
|
+
- `path=<path>` — exact path from vault root, e.g. `folder/note.md`
|
|
33
|
+
|
|
34
|
+
## Vault Targeting
|
|
35
|
+
|
|
36
|
+
Commands target the most recently focused vault by default. Use `vault=<name>` as the first parameter to target a specific vault:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
obsidian vault="My Vault" search query="test"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Common Commands
|
|
43
|
+
|
|
44
|
+
| Command | Description |
|
|
45
|
+
|---------|-------------|
|
|
46
|
+
| `read file="Note"` | Read note contents by wikilink name |
|
|
47
|
+
| `read path="folder/note.md"` | Read note contents by exact path |
|
|
48
|
+
| `create name="New Note" content="..."` | Create a new note |
|
|
49
|
+
| `append file="Note" content="..."` | Append content to a note |
|
|
50
|
+
| `search query="term" limit=10` | Full-text vault search |
|
|
51
|
+
| `search:context query="term"` | Search with surrounding line context |
|
|
52
|
+
| `daily:read` | Read today's daily note |
|
|
53
|
+
| `daily:append content="..."` | Append to today's daily note |
|
|
54
|
+
| `daily:path` | Get file path of today's daily note |
|
|
55
|
+
| `tasks daily todo` | List incomplete tasks from daily note |
|
|
56
|
+
| `tasks` | List all tasks in the vault |
|
|
57
|
+
| `tags sort=count counts` | List tags sorted by frequency |
|
|
58
|
+
| `properties` | List all frontmatter properties in vault |
|
|
59
|
+
| `properties file="Note" counts` | List properties for a specific note |
|
|
60
|
+
| `property:read name="status" file="Note"` | Read a specific property value |
|
|
61
|
+
| `property:set name="status" value="done" file="Note"` | Set a property |
|
|
62
|
+
| `backlinks file="Note" counts` | List notes that link to this note |
|
|
63
|
+
| `files folder="Projects/"` | List files in a folder |
|
|
64
|
+
| `files ext=canvas` | List files by extension |
|
|
65
|
+
| `recents` | List recently opened files |
|
|
66
|
+
| `help` | Show all available commands |
|
|
67
|
+
| `help <command>` | Show help for a specific command |
|
|
68
|
+
|
|
69
|
+
## Parameter Patterns
|
|
70
|
+
|
|
71
|
+
| Parameter | Purpose | Example |
|
|
72
|
+
|-----------|---------|---------|
|
|
73
|
+
| `file=` | Target note by wikilink name | `file="My Note"` |
|
|
74
|
+
| `path=` | Target note by exact vault path | `path="Work/Projects/alpha.md"` |
|
|
75
|
+
| `query=` | Search terms | `query="meeting notes"` |
|
|
76
|
+
| `content=` | Text content to write or append | `content="- [ ] New task"` |
|
|
77
|
+
| `name=` | Note name (create) or property name | `name="New Note"` |
|
|
78
|
+
| `value=` | Property value | `value="done"` |
|
|
79
|
+
| `limit=` | Max results to return | `limit=10` |
|
|
80
|
+
| `sort=` | Sort order | `sort=count` |
|
|
81
|
+
| `folder=` | Folder to filter by | `folder="Projects/"` |
|
|
82
|
+
| `ext=` | File extension filter | `ext=canvas` |
|
|
83
|
+
|
|
84
|
+
Quote any value that contains spaces. Use `\n` for newlines within `content=` values.
|
|
85
|
+
|
|
86
|
+
## Output Behavior
|
|
87
|
+
|
|
88
|
+
- Commands write their output to stdout
|
|
89
|
+
- Errors appear on stderr
|
|
90
|
+
- Use `--copy` on any command to copy output to clipboard
|
|
91
|
+
- Use `silent` flag to prevent files from opening in the Obsidian UI
|
|
92
|
+
- Use `total` on list commands to get a count appended to output
|
|
93
|
+
|
|
94
|
+
## Using This Knowledge with MCP Tools
|
|
95
|
+
|
|
96
|
+
When working with an Obsidian vault via this MCP server, use these tools instead of running the CLI directly:
|
|
97
|
+
|
|
98
|
+
- Read a note: `obsidian_read({ file: "Note Name" })`
|
|
99
|
+
- Read by exact path: `obsidian_read({ path: "folder/note.md" })`
|
|
100
|
+
- Search with context: `obsidian_search({ query: "term", path: "folder/", limit: 5 })`
|
|
101
|
+
- Read today's daily note: `obsidian_daily_read()`
|
|
102
|
+
- Get daily note path: `obsidian_daily_path()`
|
|
103
|
+
- Append to daily note: `obsidian_daily_append({ content: "- [ ] New task" })`
|
|
104
|
+
- Create a note: `obsidian_create({ name: "New Note", content: "# Heading\n..." })`
|
|
105
|
+
- List tasks from daily note: `obsidian_tasks({ daily: true, todo: true })`
|
|
106
|
+
- List all vault tasks: `obsidian_tasks({})`
|
|
107
|
+
- List tags by frequency: `obsidian_tags({ sort: "count" })`
|
|
108
|
+
- Read all properties for a note: `obsidian_properties({ file: "Note Name" })`
|
|
109
|
+
- Read a specific property: `obsidian_properties({ file: "Note Name", name: "status" })`
|
|
110
|
+
- Set a property: `obsidian_property_set({ name: "status", value: "done", file: "Note Name" })`
|
|
111
|
+
- List backlinks: `obsidian_backlinks({ file: "Note Name" })`
|
|
112
|
+
- List files in folder: `obsidian_files({ folder: "Projects/" })`
|
|
113
|
+
- List canvas files: `obsidian_files({ ext: "canvas" })`
|
|
114
|
+
- List recent files: `obsidian_recents()`
|
|
115
|
+
- Run any CLI command directly: `obsidian({ command: "help search" })`
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Obsidian Flavored Markdown Reference
|
|
2
|
+
|
|
3
|
+
Obsidian extends CommonMark and GitHub Flavored Markdown with wikilinks, embeds, callouts, properties, and other vault-specific syntax. This reference covers only Obsidian-specific extensions — standard Markdown (headings, bold, italic, lists, code blocks, tables) is assumed knowledge.
|
|
4
|
+
|
|
5
|
+
## Wikilinks (Internal Links)
|
|
6
|
+
|
|
7
|
+
Use wikilinks for all links within the vault. Obsidian tracks renames automatically.
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
[[Note Name]] Link to a note
|
|
11
|
+
[[Note Name|Display Text]] Custom display text
|
|
12
|
+
[[Note Name#Heading]] Link to a specific heading
|
|
13
|
+
[[Note Name#^block-id]] Link to a specific block
|
|
14
|
+
[[Note Name#^block-id|Display Text]] Link to block with custom text
|
|
15
|
+
[[#Heading in same note]] Link to heading in current note
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Define a block ID by appending `^block-id` at the end of a paragraph:
|
|
19
|
+
|
|
20
|
+
```markdown
|
|
21
|
+
This paragraph can be linked to. ^my-block-id
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For lists or quotes, place the block ID on a separate line after the block:
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
> A quote block
|
|
28
|
+
|
|
29
|
+
^quote-id
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use `[text](url)` for external URLs only — not for vault notes.
|
|
33
|
+
|
|
34
|
+
## Embeds
|
|
35
|
+
|
|
36
|
+
Prefix any wikilink with `!` to embed content inline:
|
|
37
|
+
|
|
38
|
+
```markdown
|
|
39
|
+
![[Note Name]] Embed full note
|
|
40
|
+
![[Note Name#Heading]] Embed a specific section
|
|
41
|
+
![[image.png]] Embed an image
|
|
42
|
+
![[image.png|300]] Embed image with pixel width
|
|
43
|
+
![[document.pdf#page=3]] Embed a specific PDF page
|
|
44
|
+
![[audio.mp3]] Embed audio player
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Callouts
|
|
48
|
+
|
|
49
|
+
Callouts highlight information with a colored block and icon:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
> [!note]
|
|
53
|
+
> Basic callout with default title.
|
|
54
|
+
|
|
55
|
+
> [!warning] Custom Title
|
|
56
|
+
> Callout with a custom title.
|
|
57
|
+
|
|
58
|
+
> [!tip]- Collapsed by default
|
|
59
|
+
> Use `-` after the type to collapse. Use `+` to expand by default.
|
|
60
|
+
|
|
61
|
+
> [!faq]+ Expanded by default
|
|
62
|
+
> Starts open.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Common callout types: `note`, `tip`, `info`, `warning`, `danger`, `bug`, `success`, `failure`, `question`, `example`, `quote`, `abstract`, `todo`.
|
|
66
|
+
|
|
67
|
+
Callouts can be nested:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
> [!note] Outer
|
|
71
|
+
> > [!tip] Inner
|
|
72
|
+
> > Nested callout content.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Properties (Frontmatter)
|
|
76
|
+
|
|
77
|
+
Properties are YAML metadata in a `---` delimited block at the top of the file:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
---
|
|
81
|
+
title: My Note
|
|
82
|
+
date: 2024-01-15
|
|
83
|
+
tags:
|
|
84
|
+
- project
|
|
85
|
+
- active
|
|
86
|
+
aliases:
|
|
87
|
+
- Alternative Name
|
|
88
|
+
cssclasses:
|
|
89
|
+
- custom-class
|
|
90
|
+
status: in-progress
|
|
91
|
+
priority: 2
|
|
92
|
+
done: false
|
|
93
|
+
---
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Common property types:**
|
|
97
|
+
|
|
98
|
+
| Type | YAML example |
|
|
99
|
+
|------|-------------|
|
|
100
|
+
| Text | `status: "active"` |
|
|
101
|
+
| Number | `priority: 2` |
|
|
102
|
+
| Date | `due: 2024-03-01` |
|
|
103
|
+
| Boolean | `done: false` |
|
|
104
|
+
| List | `tags:\n - item1\n - item2` |
|
|
105
|
+
| Link | `related: "[[Other Note]]"` |
|
|
106
|
+
|
|
107
|
+
**Built-in Obsidian properties:**
|
|
108
|
+
- `tags` — searchable labels (`#tag` syntax, also works inline in body)
|
|
109
|
+
- `aliases` — alternative note names for link suggestions
|
|
110
|
+
- `cssclasses` — CSS classes applied to the note in reading view
|
|
111
|
+
|
|
112
|
+
## Tags
|
|
113
|
+
|
|
114
|
+
```markdown
|
|
115
|
+
#tag Inline tag in body text
|
|
116
|
+
#nested/tag Nested tag (creates hierarchy)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Tags can also be defined in frontmatter under `tags`. Rules: letters, numbers (not first char), underscores, hyphens, and forward slashes.
|
|
120
|
+
|
|
121
|
+
## Comments
|
|
122
|
+
|
|
123
|
+
Comments are hidden in reading view:
|
|
124
|
+
|
|
125
|
+
```markdown
|
|
126
|
+
This is visible %%but this is hidden%% text.
|
|
127
|
+
|
|
128
|
+
%%
|
|
129
|
+
This entire block is hidden in reading view.
|
|
130
|
+
%%
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Highlights
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
==This text is highlighted==
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Math (LaTeX)
|
|
140
|
+
|
|
141
|
+
```markdown
|
|
142
|
+
Inline: $e^{i\pi} + 1 = 0$
|
|
143
|
+
|
|
144
|
+
Block:
|
|
145
|
+
$$
|
|
146
|
+
\frac{a}{b} = c
|
|
147
|
+
$$
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Diagrams (Mermaid)
|
|
151
|
+
|
|
152
|
+
````markdown
|
|
153
|
+
```mermaid
|
|
154
|
+
graph TD
|
|
155
|
+
A[Start] --> B{Decision}
|
|
156
|
+
B -->|Yes| C[Do this]
|
|
157
|
+
B -->|No| D[Do that]
|
|
158
|
+
```
|
|
159
|
+
````
|
|
160
|
+
|
|
161
|
+
To link Mermaid nodes to Obsidian notes, add `class NodeName internal-link;`.
|
|
162
|
+
|
|
163
|
+
## Footnotes
|
|
164
|
+
|
|
165
|
+
```markdown
|
|
166
|
+
Text with a footnote[^1].
|
|
167
|
+
|
|
168
|
+
[^1]: Footnote content goes here.
|
|
169
|
+
|
|
170
|
+
Inline footnote.^[This is the inline footnote text.]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Complete Note Example
|
|
174
|
+
|
|
175
|
+
````markdown
|
|
176
|
+
---
|
|
177
|
+
title: Project Alpha
|
|
178
|
+
date: 2024-01-15
|
|
179
|
+
tags:
|
|
180
|
+
- project
|
|
181
|
+
- active
|
|
182
|
+
status: in-progress
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# Project Alpha
|
|
186
|
+
|
|
187
|
+
This project aims to [[improve workflow]] using modern techniques.
|
|
188
|
+
|
|
189
|
+
> [!important] Key Deadline
|
|
190
|
+
> The first milestone is due on ==January 30th==.
|
|
191
|
+
|
|
192
|
+
## Tasks
|
|
193
|
+
|
|
194
|
+
- [x] Initial planning
|
|
195
|
+
- [ ] Development phase
|
|
196
|
+
|
|
197
|
+
## Notes
|
|
198
|
+
|
|
199
|
+
The algorithm uses $O(n \log n)$ sorting. See [[Algorithm Notes#Sorting]] for details.
|
|
200
|
+
|
|
201
|
+
![[Architecture Diagram.png|600]]
|
|
202
|
+
|
|
203
|
+
Reviewed in [[Meeting Notes 2024-01-10#Decisions]].
|
|
204
|
+
````
|
|
205
|
+
|
|
206
|
+
## Using This Knowledge with MCP Tools
|
|
207
|
+
|
|
208
|
+
When creating or editing notes through this MCP server, apply these patterns:
|
|
209
|
+
|
|
210
|
+
- Read a note (see its markdown): `obsidian_read({ file: "Note Name" })`
|
|
211
|
+
- Create a note with frontmatter:
|
|
212
|
+
```
|
|
213
|
+
obsidian_create({
|
|
214
|
+
name: "New Note",
|
|
215
|
+
content: "---\ntags:\n - project\nstatus: active\n---\n# Heading\n\nContent here."
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
- Append a callout to daily note:
|
|
219
|
+
```
|
|
220
|
+
obsidian_daily_append({ content: "> [!tip] Reminder\n> Don't forget the meeting." })
|
|
221
|
+
```
|
|
222
|
+
- Set a frontmatter property: `obsidian_property_set({ name: "status", value: "done", file: "Note Name" })`
|
|
223
|
+
- Search by tag: `obsidian_search({ query: "tag:#project" })`
|
|
224
|
+
- Find notes linking to another: `obsidian_backlinks({ file: "Note Name" })`
|
package/server.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Requirements:
|
|
15
15
|
* - Obsidian must be running with the CLI plugin active.
|
|
16
|
-
* - The
|
|
16
|
+
* - The CLI binary is auto-discovered from common macOS locations.
|
|
17
|
+
* Set OBSIDIAN_CLI_PATH to override.
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -24,13 +25,25 @@ import { promisify } from "node:util";
|
|
|
24
25
|
import { readFileSync, mkdirSync, existsSync } from "node:fs";
|
|
25
26
|
import { load as yamlLoad } from "js-yaml";
|
|
26
27
|
import { homedir } from "node:os";
|
|
27
|
-
import { join } from "node:path";
|
|
28
|
+
import { join, dirname } from "node:path";
|
|
29
|
+
import { fileURLToPath } from "node:url";
|
|
28
30
|
import { exec } from "node:child_process";
|
|
29
31
|
|
|
30
32
|
const execFileAsync = promisify(execFile);
|
|
31
33
|
const execAsync = promisify(exec);
|
|
32
34
|
|
|
33
|
-
const
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const PROMPTS_DIR = join(__dirname, "prompts");
|
|
37
|
+
|
|
38
|
+
const promptContent = {
|
|
39
|
+
"obsidian-cli": readFileSync(join(PROMPTS_DIR, "obsidian-cli.md"), "utf8"),
|
|
40
|
+
"obsidian-markdown": readFileSync(join(PROMPTS_DIR, "obsidian-markdown.md"), "utf8"),
|
|
41
|
+
"obsidian-bases": readFileSync(join(PROMPTS_DIR, "obsidian-bases.md"), "utf8"),
|
|
42
|
+
"obsidian-canvas": readFileSync(join(PROMPTS_DIR, "obsidian-canvas.md"), "utf8"),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const configBase = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
46
|
+
const CONFIG_DIR = join(configBase, "mcp-obsidian-cli");
|
|
34
47
|
const CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
|
|
35
48
|
|
|
36
49
|
function loadConfig() {
|
|
@@ -58,8 +71,37 @@ function loadConfig() {
|
|
|
58
71
|
return config;
|
|
59
72
|
}
|
|
60
73
|
|
|
74
|
+
const KNOWN_CLI_PATHS = [
|
|
75
|
+
"/Applications/Obsidian.app/Contents/MacOS/obsidian",
|
|
76
|
+
join(homedir(), "Applications/Obsidian.app/Contents/MacOS/obsidian"),
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
async function resolveCliPath(configured) {
|
|
80
|
+
if (configured !== "obsidian") return configured;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await execAsync("which obsidian", { timeout: 2000 });
|
|
84
|
+
return configured;
|
|
85
|
+
} catch { /* not on PATH */ }
|
|
86
|
+
|
|
87
|
+
for (const p of KNOWN_CLI_PATHS) {
|
|
88
|
+
if (existsSync(p)) return p;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const { stdout } = await execAsync(
|
|
93
|
+
"ps aux | grep -i obsidian | grep -v grep | grep -v Helper",
|
|
94
|
+
{ timeout: 2000 }
|
|
95
|
+
);
|
|
96
|
+
const match = stdout.match(/(\S*\/Contents\/MacOS\/obsidian)/i);
|
|
97
|
+
if (match && existsSync(match[1])) return match[1];
|
|
98
|
+
} catch { /* no running process */ }
|
|
99
|
+
|
|
100
|
+
return configured;
|
|
101
|
+
}
|
|
102
|
+
|
|
61
103
|
const config = loadConfig();
|
|
62
|
-
const CLI = config.cliPath;
|
|
104
|
+
const CLI = await resolveCliPath(config.cliPath);
|
|
63
105
|
const VAULT = config.vault;
|
|
64
106
|
const TIMEOUT_MS = config.timeoutMs;
|
|
65
107
|
|
|
@@ -179,7 +221,7 @@ async function runTool(argString) {
|
|
|
179
221
|
|
|
180
222
|
const server = new McpServer({
|
|
181
223
|
name: "obsidian-mcp",
|
|
182
|
-
version: "1.
|
|
224
|
+
version: "1.1.0",
|
|
183
225
|
capabilities: { tools: {} },
|
|
184
226
|
});
|
|
185
227
|
|
|
@@ -205,28 +247,28 @@ Examples:
|
|
|
205
247
|
|
|
206
248
|
server.tool(
|
|
207
249
|
"obsidian_daily_read",
|
|
208
|
-
"Read today's daily note contents.",
|
|
250
|
+
"Read today's daily note contents.\n\nReturns the full markdown content of today's daily note. Returns an error if no daily note exists for today.",
|
|
209
251
|
{},
|
|
210
252
|
async () => runTool("daily:read"),
|
|
211
253
|
);
|
|
212
254
|
|
|
213
255
|
server.tool(
|
|
214
256
|
"obsidian_daily_path",
|
|
215
|
-
"Get the file path of today's daily note.",
|
|
257
|
+
"Get the file path of today's daily note.\n\nReturns the vault-relative path (e.g. 'Daily/2026-04-03.md'). Useful for constructing paths for other tools.",
|
|
216
258
|
{},
|
|
217
259
|
async () => runTool("daily:path"),
|
|
218
260
|
);
|
|
219
261
|
|
|
220
262
|
server.tool(
|
|
221
263
|
"obsidian_daily_append",
|
|
222
|
-
"Append content to today's daily note
|
|
264
|
+
"Append content to today's daily note.\n\nParameters:\n content (required) — markdown text to append at the end of today's daily note\n\nExamples:\n obsidian_daily_append({ content: \"- Meeting with team at 3pm\" })\n obsidian_daily_append({ content: \"> [!tip] Remember\\n> Review PR before EOD\" })",
|
|
223
265
|
{ content: z.string().describe("Content to append") },
|
|
224
266
|
async ({ content }) => runTool(`daily:append content="${content.replace(/"/g, '\\"')}"`),
|
|
225
267
|
);
|
|
226
268
|
|
|
227
269
|
server.tool(
|
|
228
270
|
"obsidian_read",
|
|
229
|
-
"Read a note by file name (wikilink-style) or exact path.",
|
|
271
|
+
"Read a note by file name (wikilink-style) or exact path.\n\nParameters:\n file (optional) — note name using wikilink resolution (e.g. 'My Note')\n path (optional) — exact vault-relative path (e.g. 'folder/My Note.md')\n One of file or path is required.\n\nExamples:\n obsidian_read({ file: \"Meeting Notes\" })\n obsidian_read({ path: \"Projects/todo.md\" })",
|
|
230
272
|
{
|
|
231
273
|
file: z.string().optional().describe("File name (wikilink resolution)"),
|
|
232
274
|
path: z.string().optional().describe("Exact file path"),
|
|
@@ -240,7 +282,7 @@ server.tool(
|
|
|
240
282
|
|
|
241
283
|
server.tool(
|
|
242
284
|
"obsidian_search",
|
|
243
|
-
"Full-text search across the vault with line context
|
|
285
|
+
"Full-text search across the vault with line context.\n\nParameters:\n query (required) — search terms, supports Obsidian query syntax\n path (optional) — restrict results to a folder path\n limit (optional) — max number of files to return\n\nExamples:\n obsidian_search({ query: \"meeting notes\" })\n obsidian_search({ query: \"project status\", path: \"Work/\", limit: 5 })",
|
|
244
286
|
{
|
|
245
287
|
query: z.string().describe("Search query"),
|
|
246
288
|
path: z.string().optional().describe("Limit to folder"),
|
|
@@ -256,7 +298,7 @@ server.tool(
|
|
|
256
298
|
|
|
257
299
|
server.tool(
|
|
258
300
|
"obsidian_tags",
|
|
259
|
-
"List tags in the vault with counts
|
|
301
|
+
"List tags in the vault with counts.\n\nParameters:\n sort (optional) — 'name' or 'count' (default: name)\n\nExamples:\n obsidian_tags({})\n obsidian_tags({ sort: \"count\" })",
|
|
260
302
|
{
|
|
261
303
|
sort: z.enum(["name", "count"]).optional().describe("Sort order"),
|
|
262
304
|
},
|
|
@@ -269,7 +311,7 @@ server.tool(
|
|
|
269
311
|
|
|
270
312
|
server.tool(
|
|
271
313
|
"obsidian_tasks",
|
|
272
|
-
"List tasks
|
|
314
|
+
"List tasks from vault notes.\n\nParameters:\n daily (optional) — true to show only today's daily note tasks\n todo (optional) — true to show only incomplete tasks\n done (optional) — true to show only completed tasks\n path (optional) — filter by file path\n\nExamples:\n obsidian_tasks({ daily: true })\n obsidian_tasks({ todo: true, path: \"Projects/\" })",
|
|
273
315
|
{
|
|
274
316
|
daily: z.boolean().optional().describe("Show only daily note tasks"),
|
|
275
317
|
todo: z.boolean().optional().describe("Show incomplete tasks only"),
|
|
@@ -288,7 +330,7 @@ server.tool(
|
|
|
288
330
|
|
|
289
331
|
server.tool(
|
|
290
332
|
"obsidian_properties",
|
|
291
|
-
"List or read frontmatter properties
|
|
333
|
+
"List or read frontmatter properties.\n\nParameters:\n file (optional) — note name for wikilink resolution\n path (optional) — exact file path\n name (optional) — specific property name to read (requires file or path)\n\nExamples:\n obsidian_properties({}) — list all properties with counts\n obsidian_properties({ file: \"My Note\" }) — properties of a specific note\n obsidian_properties({ file: \"My Note\", name: \"status\" }) — read one property",
|
|
292
334
|
{
|
|
293
335
|
file: z.string().optional().describe("File name"),
|
|
294
336
|
path: z.string().optional().describe("File path"),
|
|
@@ -310,7 +352,7 @@ server.tool(
|
|
|
310
352
|
|
|
311
353
|
server.tool(
|
|
312
354
|
"obsidian_create",
|
|
313
|
-
"Create a new note.",
|
|
355
|
+
"Create a new note.\n\nParameters:\n name (optional) — file name for the new note\n path (optional) — vault-relative path\n content (optional) — initial markdown content\n template (optional) — template name to use\n\nExamples:\n obsidian_create({ name: \"Meeting 2026-04-03\", content: \"# Meeting Notes\\n\\n- Attendees: ...\" })\n obsidian_create({ path: \"Projects/new-idea.md\", template: \"project\" })",
|
|
314
356
|
{
|
|
315
357
|
name: z.string().optional().describe("File name"),
|
|
316
358
|
path: z.string().optional().describe("File path"),
|
|
@@ -329,7 +371,7 @@ server.tool(
|
|
|
329
371
|
|
|
330
372
|
server.tool(
|
|
331
373
|
"obsidian_property_set",
|
|
332
|
-
"Set a frontmatter property on a note.",
|
|
374
|
+
"Set a frontmatter property on a note.\n\nParameters:\n name (required) — property name\n value (required) — property value\n file (optional) — note name (wikilink resolution)\n path (optional) — exact file path\n One of file or path is required.\n\nExamples:\n obsidian_property_set({ name: \"status\", value: \"done\", file: \"My Task\" })\n obsidian_property_set({ name: \"tags\", value: \"project, active\", path: \"Work/todo.md\" })",
|
|
333
375
|
{
|
|
334
376
|
name: z.string().describe("Property name"),
|
|
335
377
|
value: z.string().describe("Property value"),
|
|
@@ -345,7 +387,7 @@ server.tool(
|
|
|
345
387
|
|
|
346
388
|
server.tool(
|
|
347
389
|
"obsidian_backlinks",
|
|
348
|
-
"List backlinks to a note.",
|
|
390
|
+
"List backlinks to a note.\n\nParameters:\n file (optional) — note name (wikilink resolution)\n path (optional) — exact file path\n\nExamples:\n obsidian_backlinks({ file: \"Project Plan\" })\n obsidian_backlinks({ path: \"Ideas/brainstorm.md\" })",
|
|
349
391
|
{
|
|
350
392
|
file: z.string().optional().describe("File name"),
|
|
351
393
|
path: z.string().optional().describe("File path"),
|
|
@@ -358,7 +400,7 @@ server.tool(
|
|
|
358
400
|
|
|
359
401
|
server.tool(
|
|
360
402
|
"obsidian_files",
|
|
361
|
-
"List files in the vault or a specific folder.",
|
|
403
|
+
"List files in the vault or a specific folder.\n\nParameters:\n folder (optional) — filter by folder path\n ext (optional) — filter by file extension (e.g. 'md', 'canvas')\n\nExamples:\n obsidian_files({})\n obsidian_files({ folder: \"Projects/\", ext: \"md\" })",
|
|
362
404
|
{
|
|
363
405
|
folder: z.string().optional().describe("Filter by folder path"),
|
|
364
406
|
ext: z.string().optional().describe("Filter by extension"),
|
|
@@ -373,11 +415,52 @@ server.tool(
|
|
|
373
415
|
|
|
374
416
|
server.tool(
|
|
375
417
|
"obsidian_recents",
|
|
376
|
-
"List recently opened files.",
|
|
418
|
+
"List recently opened files.\n\nReturns the most recently opened files in the vault, ordered by last access time.",
|
|
377
419
|
{},
|
|
378
420
|
async () => runTool("recents"),
|
|
379
421
|
);
|
|
380
422
|
|
|
423
|
+
server.tool(
|
|
424
|
+
"obsidian_help",
|
|
425
|
+
"Get Obsidian reference documentation on a topic.\n\nParameters:\n topic (required) — one of: cli, markdown, bases, canvas\n\nExamples:\n obsidian_help({ topic: \"markdown\" }) — wikilinks, embeds, callouts, properties, tags\n obsidian_help({ topic: \"bases\" }) — Bases YAML schema, filters, formulas, views\n obsidian_help({ topic: \"canvas\" }) — JSON Canvas nodes, edges, colors, layout\n obsidian_help({ topic: \"cli\" }) — CLI command syntax and parameter patterns",
|
|
426
|
+
{ topic: z.enum(["cli", "markdown", "bases", "canvas"]).describe("Reference topic") },
|
|
427
|
+
async ({ topic }) => ({
|
|
428
|
+
content: [{ type: "text", text: promptContent[`obsidian-${topic}`] }],
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// ---- MCP Prompts -----------------------------------------------------------
|
|
433
|
+
|
|
434
|
+
const promptMeta = {
|
|
435
|
+
"obsidian-cli": {
|
|
436
|
+
title: "Obsidian CLI Reference",
|
|
437
|
+
description: "CLI usage patterns, parameter syntax, and command examples for the Obsidian CLI. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
438
|
+
},
|
|
439
|
+
"obsidian-markdown": {
|
|
440
|
+
title: "Obsidian Flavored Markdown Reference",
|
|
441
|
+
description: "Wikilinks, embeds, callouts, properties, tags, and other Obsidian-specific markdown syntax. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
442
|
+
},
|
|
443
|
+
"obsidian-bases": {
|
|
444
|
+
title: "Obsidian Bases Reference",
|
|
445
|
+
description: "Bases syntax for database-like views: filters, formulas, view types, and summaries. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
446
|
+
},
|
|
447
|
+
"obsidian-canvas": {
|
|
448
|
+
title: "JSON Canvas Reference",
|
|
449
|
+
description: "JSON Canvas format for visual canvases: node types, edges, groups, and layout. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
for (const [name, content] of Object.entries(promptContent)) {
|
|
454
|
+
const meta = promptMeta[name];
|
|
455
|
+
server.registerPrompt(
|
|
456
|
+
name,
|
|
457
|
+
{ title: meta.title, description: meta.description },
|
|
458
|
+
() => ({
|
|
459
|
+
messages: [{ role: "user", content: { type: "text", text: content } }],
|
|
460
|
+
})
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
381
464
|
// ---- Start ---------------------------------------------------------------
|
|
382
465
|
|
|
383
466
|
async function main() {
|