obsidian-mcp-server 3.5.3 → 3.5.5

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 (86) hide show
  1. package/AGENTS.md +47 -6
  2. package/CLAUDE.md +47 -6
  3. package/Dockerfile +3 -2
  4. package/README.md +144 -100
  5. package/changelog/3.5.x/3.5.3.md +4 -3
  6. package/changelog/3.5.x/3.5.4.md +42 -0
  7. package/changelog/3.5.x/3.5.5.md +20 -0
  8. package/changelog/template.md +7 -7
  9. package/dist/config/server-config.d.ts +1 -1
  10. package/dist/config/server-config.d.ts.map +1 -1
  11. package/dist/config/server-config.js +9 -2
  12. package/dist/config/server-config.js.map +1 -1
  13. package/dist/index.js +9 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts.map +1 -1
  16. package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js +19 -1
  17. package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/_shared/schemas.js +1 -1
  19. package/dist/mcp-server/tools/definitions/_shared/schemas.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/index.d.ts +157 -65
  21. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +13 -2
  23. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +14 -3
  25. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +9 -0
  27. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -1
  28. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +9 -0
  29. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -1
  30. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts +1 -0
  31. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts.map +1 -1
  32. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js +1 -0
  33. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js.map +1 -1
  34. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +9 -0
  35. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
  36. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +19 -26
  37. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
  38. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +3 -0
  39. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
  40. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +3 -0
  41. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
  42. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +13 -0
  43. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
  44. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +19 -1
  45. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -1
  46. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +17 -2
  47. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
  48. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +22 -3
  49. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -1
  50. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +4 -0
  51. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -1
  52. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +4 -0
  53. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -1
  54. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +13 -2
  55. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -1
  56. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +14 -3
  57. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -1
  58. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +10 -2
  59. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
  60. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +10 -2
  61. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -1
  62. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +12 -5
  63. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -1
  64. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +29 -24
  65. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
  66. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +12 -2
  67. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
  68. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +20 -10
  69. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -1
  70. package/dist/services/obsidian/frontmatter-ops.d.ts +44 -11
  71. package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -1
  72. package/dist/services/obsidian/frontmatter-ops.js +370 -69
  73. package/dist/services/obsidian/frontmatter-ops.js.map +1 -1
  74. package/dist/services/obsidian/obsidian-service.d.ts +26 -5
  75. package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
  76. package/dist/services/obsidian/obsidian-service.js +469 -85
  77. package/dist/services/obsidian/obsidian-service.js.map +1 -1
  78. package/dist/services/obsidian/section-extractor.d.ts +33 -3
  79. package/dist/services/obsidian/section-extractor.d.ts.map +1 -1
  80. package/dist/services/obsidian/section-extractor.js +115 -75
  81. package/dist/services/obsidian/section-extractor.js.map +1 -1
  82. package/dist/services/obsidian/types.d.ts +11 -4
  83. package/dist/services/obsidian/types.d.ts.map +1 -1
  84. package/manifest.json +1 -1
  85. package/package.json +9 -9
  86. package/server.json +3 -3
@@ -107,36 +107,43 @@ export declare const readToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
107
107
  readonly recovery: "Pass `section: { type, target }` (e.g. `{ type: \"heading\", target: \"Intro\" }`), or use `format: \"full\"` / `\"document-map\"` instead.";
108
108
  }, {
109
109
  readonly reason: "path_forbidden";
110
+ readonly thrownBy: "service";
110
111
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
111
112
  readonly when: "The target path is outside OBSIDIAN_READ_PATHS (and OBSIDIAN_WRITE_PATHS, since write paths imply read access).";
112
113
  readonly recovery: "Use a path inside the configured read scope. The error data echoes the active scope.";
113
114
  }, {
114
115
  readonly reason: "note_missing";
116
+ readonly thrownBy: "service";
115
117
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
116
118
  readonly when: "The vault path does not resolve to an existing note.";
117
119
  readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
118
120
  }, {
119
121
  readonly reason: "ambiguous_path";
122
+ readonly thrownBy: "service";
120
123
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
121
124
  readonly when: "The parent directory contains multiple files whose names differ only in case (case-sensitive filesystems only).";
122
125
  readonly recovery: "Retry with one of the exact paths listed in `matches` on the error data.";
123
126
  }, {
124
127
  readonly reason: "no_active_file";
128
+ readonly thrownBy: "service";
125
129
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
126
130
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
127
131
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
128
132
  }, {
129
133
  readonly reason: "periodic_unsupported";
134
+ readonly thrownBy: "service";
130
135
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
131
136
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
132
137
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
133
138
  }, {
134
139
  readonly reason: "periodic_not_found";
140
+ readonly thrownBy: "service";
135
141
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
136
142
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
137
143
  readonly recovery: "Create the periodic note first or pass an explicit path target.";
138
144
  }, {
139
145
  readonly reason: "periodic_disabled";
146
+ readonly thrownBy: "service";
140
147
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
141
148
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
142
149
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
@@ -147,11 +154,13 @@ export declare const readToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
147
154
  readonly recovery: "Call obsidian_get_note with format \"document-map\" to list available headings, blocks, and frontmatter fields, then retry with one of those locators.";
148
155
  }, {
149
156
  readonly reason: "path_is_directory";
157
+ readonly thrownBy: "service";
150
158
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
151
159
  readonly when: "The supplied path names a folder rather than a note file.";
152
160
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then retry with the full path of one of the files it returns.";
153
161
  }, {
154
162
  readonly reason: "path_traversal";
163
+ readonly thrownBy: "service";
155
164
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
156
165
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
157
166
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -228,16 +237,19 @@ export declare const readToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
228
237
  readonly recovery: "Avoid nested quantifiers like `(a+)+` or `(.*)*`. Use a simpler pattern (e.g. `^Project.*\\.md$`), or omit nameRegex to disable filtering.";
229
238
  }, {
230
239
  readonly reason: "path_forbidden";
240
+ readonly thrownBy: "service";
231
241
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
232
242
  readonly when: "The supplied `path` is outside OBSIDIAN_READ_PATHS (root listings always pass; specific subdirectories must be readable).";
233
243
  readonly recovery: "List a directory inside the configured read scope, or omit `path` to list from the vault root. The error data echoes the active scope.";
234
244
  }, {
235
245
  readonly reason: "directory_missing";
246
+ readonly thrownBy: "service";
236
247
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
237
248
  readonly when: "No listable directory at the supplied `path` — either it does not exist, or it exists and currently holds no files. Sub-directories that disappear mid-walk are silently skipped, so only the root path surfaces this error.";
238
249
  readonly recovery: "List the parent directory to check the spelling and casing. A folder that exists but holds no files reports the same way, since the Local REST API omits empty folders from its listings — add a file to it, or confirm from the parent that the folder is there.";
239
250
  }, {
240
251
  readonly reason: "path_is_file";
252
+ readonly thrownBy: "service";
241
253
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
242
254
  readonly when: "The supplied `path` names a file rather than a directory.";
243
255
  readonly recovery: "Read the file with obsidian_get_note instead, or list its parent directory to browse alongside it.";
@@ -253,6 +265,7 @@ export declare const readToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
253
265
  createdIfMissing: import("zod").ZodBoolean;
254
266
  }, import("zod/v4/core").$strip>, readonly [{
255
267
  readonly reason: "path_forbidden";
268
+ readonly thrownBy: "service";
256
269
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
257
270
  readonly when: "The path is outside OBSIDIAN_READ_PATHS, or — when the file does not exist and `failIfMissing: false` would have Obsidian create it — outside OBSIDIAN_WRITE_PATHS or blocked by OBSIDIAN_READ_ONLY=true.";
258
271
  readonly recovery: "Open a path inside the configured scope. The error data echoes the active scope and whether read or write access was the one denied.";
@@ -263,16 +276,19 @@ export declare const readToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
263
276
  readonly recovery: "Verify the path with obsidian_list_notes or obsidian_search_notes first — a typo would otherwise materialize as an empty file. If creation is intended, retry with failIfMissing: false.";
264
277
  }, {
265
278
  readonly reason: "ambiguous_path";
279
+ readonly thrownBy: "service";
266
280
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
267
281
  readonly when: "The parent directory contains multiple files whose names differ only in case (case-sensitive filesystems only).";
268
282
  readonly recovery: "Retry with one of the exact paths listed in `matches` on the error data.";
269
283
  }, {
270
284
  readonly reason: "path_is_directory";
285
+ readonly thrownBy: "service";
271
286
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
272
287
  readonly when: "The supplied path names a folder rather than a file.";
273
288
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then open one of the files it returns.";
274
289
  }, {
275
290
  readonly reason: "path_traversal";
291
+ readonly thrownBy: "service";
276
292
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
277
293
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
278
294
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -295,98 +311,90 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
295
311
  }>;
296
312
  date: import("zod").ZodOptional<import("zod").ZodString>;
297
313
  }, import("zod/v4/core").$strip>], "type">;
298
- section: import("zod").ZodObject<{
314
+ content: import("zod").ZodString;
315
+ section: import("zod").ZodOptional<import("zod").ZodObject<{
299
316
  type: import("zod").ZodEnum<{
300
317
  block: "block";
301
318
  frontmatter: "frontmatter";
302
319
  heading: "heading";
303
320
  }>;
304
321
  target: import("zod").ZodString;
305
- }, import("zod/v4/core").$strip>;
306
- operation: import("zod").ZodEnum<{
307
- append: "append";
308
- prepend: "prepend";
309
- replace: "replace";
310
- }>;
311
- content: import("zod").ZodString;
322
+ }, import("zod/v4/core").$strip>>;
312
323
  contentType: import("zod").ZodDefault<import("zod").ZodEnum<{
313
324
  json: "json";
314
325
  markdown: "markdown";
315
326
  }>>;
316
- patchOptions: import("zod").ZodOptional<import("zod").ZodObject<{
317
- createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
318
- applyIfContentPreexists: import("zod").ZodDefault<import("zod").ZodBoolean>;
319
- trimTargetWhitespace: import("zod").ZodDefault<import("zod").ZodBoolean>;
320
- }, import("zod/v4/core").$strip>>;
327
+ createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
321
328
  }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
322
329
  path: import("zod").ZodString;
323
- section: import("zod").ZodObject<{
324
- type: import("zod").ZodEnum<{
325
- block: "block";
326
- frontmatter: "frontmatter";
327
- heading: "heading";
328
- }>;
329
- target: import("zod").ZodString;
330
- }, import("zod/v4/core").$strip>;
331
- operation: import("zod").ZodEnum<{
332
- append: "append";
333
- prepend: "prepend";
334
- replace: "replace";
335
- }>;
330
+ sectionTargeted: import("zod").ZodBoolean;
331
+ sectionTarget: import("zod").ZodOptional<import("zod").ZodString>;
332
+ created: import("zod").ZodBoolean;
336
333
  previousSizeInBytes: import("zod").ZodNumber;
337
334
  currentSizeInBytes: import("zod").ZodNumber;
338
335
  }, import("zod/v4/core").$strip>, readonly [{
339
336
  readonly reason: "path_forbidden";
337
+ readonly thrownBy: "service";
340
338
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
341
339
  readonly when: "The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes.";
342
340
  readonly recovery: "Use a path inside the configured write scope. The error data echoes the active scope.";
343
341
  }, {
344
342
  readonly reason: "note_missing";
343
+ readonly thrownBy: "service";
345
344
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
346
- readonly when: "The vault path does not resolve to an existing note.";
347
- readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
345
+ readonly when: "Section append targets a path that does not resolve to an existing note (PATCH requires the file to exist).";
346
+ readonly recovery: "Verify the path with obsidian_list_notes, or omit `section` to fall back to whole-file append (which creates the note if missing).";
348
347
  }, {
349
348
  readonly reason: "no_active_file";
349
+ readonly thrownBy: "service";
350
350
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
351
351
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
352
352
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
353
353
  }, {
354
354
  readonly reason: "periodic_unsupported";
355
+ readonly thrownBy: "service";
355
356
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
356
357
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
357
358
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
358
359
  }, {
359
360
  readonly reason: "periodic_not_found";
361
+ readonly thrownBy: "service";
360
362
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
361
363
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
362
364
  readonly recovery: "Create the periodic note first or pass an explicit path target.";
363
365
  }, {
364
366
  readonly reason: "periodic_disabled";
367
+ readonly thrownBy: "service";
365
368
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
366
369
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
367
370
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
368
371
  }, {
369
372
  readonly reason: "section_target_missing";
373
+ readonly thrownBy: "service";
370
374
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
371
- readonly when: "The named heading/block/frontmatter field does not exist in the note. Use `obsidian_get_note` with `format: \"document-map\"` to discover available targets.";
372
- readonly recovery: "Call obsidian_get_note with format document-map to discover the available targets.";
375
+ readonly when: "`section` was provided but the named heading/block/frontmatter field does not exist in the note.";
376
+ readonly recovery: "Call obsidian_get_note with format document-map to discover available targets, or pass createTargetIfMissing: true to bring it into existence.";
373
377
  }, {
374
378
  readonly reason: "ambiguous_section";
379
+ readonly thrownBy: "service";
375
380
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
376
- readonly when: "A bare heading leaf name matches more than one heading in the note, so the write target is undetermined.";
377
- readonly recovery: "Retry with one of the full Parent::Child heading paths listed in `candidates` on the error data.";
381
+ readonly when: "A bare heading leaf name matches more than one heading in the note, or the resolved full heading path occurs more than once, so the append target is undetermined.";
382
+ readonly recovery: "Retry with a distinct full Parent::Child heading path from `candidates` on the error data; when every candidate is the same path, rename the repeated headings first.";
378
383
  }, {
379
384
  readonly reason: "content_preexists";
385
+ readonly thrownBy: "service";
380
386
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
381
- readonly when: "The supplied content already appears at the target — the patch was rejected to keep retries idempotent (the default).";
382
- readonly recovery: "Pass `patchOptions.applyIfContentPreexists: true` to force-apply over preexisting content, or change the content to something not already present.";
387
+ readonly when: "Section append where the supplied content already appears at the target — rejected to keep retries idempotent (the default for the section path).";
388
+ readonly recovery: "Change the content to something not already present at the target, or use obsidian_patch_note with `patchOptions.applyIfContentPreexists: true` if a duplicate is intended.";
383
389
  }, {
384
390
  readonly reason: "path_is_directory";
391
+ readonly thrownBy: "service";
385
392
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
386
393
  readonly when: "The supplied path names a folder rather than a note file.";
387
394
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then retry with the full path of one of the files it returns.";
388
395
  }, {
389
396
  readonly reason: "path_traversal";
397
+ readonly thrownBy: "service";
390
398
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
391
399
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
392
400
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -407,79 +415,109 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
407
415
  }>;
408
416
  date: import("zod").ZodOptional<import("zod").ZodString>;
409
417
  }, import("zod/v4/core").$strip>], "type">;
410
- content: import("zod").ZodString;
411
- section: import("zod").ZodOptional<import("zod").ZodObject<{
418
+ section: import("zod").ZodObject<{
412
419
  type: import("zod").ZodEnum<{
413
420
  block: "block";
414
421
  frontmatter: "frontmatter";
415
422
  heading: "heading";
416
423
  }>;
417
424
  target: import("zod").ZodString;
418
- }, import("zod/v4/core").$strip>>;
425
+ }, import("zod/v4/core").$strip>;
426
+ operation: import("zod").ZodEnum<{
427
+ append: "append";
428
+ prepend: "prepend";
429
+ replace: "replace";
430
+ }>;
431
+ content: import("zod").ZodString;
419
432
  contentType: import("zod").ZodDefault<import("zod").ZodEnum<{
420
433
  json: "json";
421
434
  markdown: "markdown";
422
435
  }>>;
423
- overwrite: import("zod").ZodDefault<import("zod").ZodBoolean>;
436
+ patchOptions: import("zod").ZodOptional<import("zod").ZodObject<{
437
+ createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
438
+ applyIfContentPreexists: import("zod").ZodDefault<import("zod").ZodBoolean>;
439
+ trimTargetWhitespace: import("zod").ZodDefault<import("zod").ZodBoolean>;
440
+ }, import("zod/v4/core").$strip>>;
424
441
  }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
425
442
  path: import("zod").ZodString;
426
- sectionTargeted: import("zod").ZodBoolean;
427
- sectionTarget: import("zod").ZodOptional<import("zod").ZodString>;
428
- created: import("zod").ZodBoolean;
443
+ section: import("zod").ZodObject<{
444
+ type: import("zod").ZodEnum<{
445
+ block: "block";
446
+ frontmatter: "frontmatter";
447
+ heading: "heading";
448
+ }>;
449
+ target: import("zod").ZodString;
450
+ }, import("zod/v4/core").$strip>;
451
+ operation: import("zod").ZodEnum<{
452
+ append: "append";
453
+ prepend: "prepend";
454
+ replace: "replace";
455
+ }>;
429
456
  previousSizeInBytes: import("zod").ZodNumber;
430
457
  currentSizeInBytes: import("zod").ZodNumber;
431
458
  }, import("zod/v4/core").$strip>, readonly [{
432
- readonly reason: "file_exists";
433
- readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
434
- readonly when: "Whole-file write was attempted against an existing note and `overwrite` was not set to `true`.";
435
- readonly recovery: "Retry with overwrite true or use obsidian_patch_note for in-place edits.";
436
- }, {
437
459
  readonly reason: "path_forbidden";
460
+ readonly thrownBy: "service";
438
461
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
439
462
  readonly when: "The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes.";
440
463
  readonly recovery: "Use a path inside the configured write scope. The error data echoes the active scope.";
441
464
  }, {
442
465
  readonly reason: "note_missing";
466
+ readonly thrownBy: "service";
443
467
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
444
- readonly when: "Section replace targets a path that does not resolve to an existing note (PATCH requires the file to exist).";
445
- readonly recovery: "Verify the path with obsidian_list_notes, or omit `section` to fall back to whole-file write (which creates the note when it is absent).";
468
+ readonly when: "The vault path does not resolve to an existing note.";
469
+ readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
446
470
  }, {
447
471
  readonly reason: "no_active_file";
472
+ readonly thrownBy: "service";
448
473
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
449
474
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
450
475
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
451
476
  }, {
452
477
  readonly reason: "periodic_unsupported";
478
+ readonly thrownBy: "service";
453
479
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
454
480
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
455
481
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
456
482
  }, {
457
483
  readonly reason: "periodic_not_found";
484
+ readonly thrownBy: "service";
458
485
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
459
486
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
460
487
  readonly recovery: "Create the periodic note first or pass an explicit path target.";
461
488
  }, {
462
489
  readonly reason: "periodic_disabled";
490
+ readonly thrownBy: "service";
463
491
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
464
492
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
465
493
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
466
494
  }, {
467
495
  readonly reason: "section_target_missing";
496
+ readonly thrownBy: "service";
468
497
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
469
- readonly when: "`section` was provided but the named heading/block/frontmatter field does not exist in the note.";
470
- readonly recovery: "Call obsidian_get_note with format document-map to discover available targets.";
498
+ readonly when: "The named heading/block/frontmatter field does not exist in the note. Use `obsidian_get_note` with `format: \"document-map\"` to discover available targets.";
499
+ readonly recovery: "Call obsidian_get_note with format document-map to discover the available targets.";
471
500
  }, {
472
501
  readonly reason: "ambiguous_section";
502
+ readonly thrownBy: "service";
473
503
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
474
- readonly when: "A bare heading leaf name matches more than one heading in the note, so the replacement target is undetermined.";
475
- readonly recovery: "Retry with one of the full Parent::Child heading paths listed in `candidates` on the error data.";
504
+ readonly when: "A bare heading leaf name matches more than one heading in the note, or the resolved full heading path occurs more than once, so the write target is undetermined.";
505
+ readonly recovery: "Retry with a distinct full Parent::Child heading path from `candidates` on the error data; when every candidate is the same path, rename the repeated headings first.";
506
+ }, {
507
+ readonly reason: "content_preexists";
508
+ readonly thrownBy: "service";
509
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
510
+ readonly when: "The supplied content already appears at the target — the patch was rejected to keep retries idempotent (the default).";
511
+ readonly recovery: "Pass `patchOptions.applyIfContentPreexists: true` to force-apply over preexisting content, or change the content to something not already present.";
476
512
  }, {
477
513
  readonly reason: "path_is_directory";
514
+ readonly thrownBy: "service";
478
515
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
479
516
  readonly when: "The supplied path names a folder rather than a note file.";
480
517
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then retry with the full path of one of the files it returns.";
481
518
  }, {
482
519
  readonly reason: "path_traversal";
520
+ readonly thrownBy: "service";
483
521
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
484
522
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
485
523
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -513,7 +551,7 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
513
551
  json: "json";
514
552
  markdown: "markdown";
515
553
  }>>;
516
- createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
554
+ overwrite: import("zod").ZodDefault<import("zod").ZodBoolean>;
517
555
  }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
518
556
  path: import("zod").ZodString;
519
557
  sectionTargeted: import("zod").ZodBoolean;
@@ -522,57 +560,67 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
522
560
  previousSizeInBytes: import("zod").ZodNumber;
523
561
  currentSizeInBytes: import("zod").ZodNumber;
524
562
  }, import("zod/v4/core").$strip>, readonly [{
563
+ readonly reason: "file_exists";
564
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
565
+ readonly when: "Whole-file write was attempted against an existing note and `overwrite` was not set to `true`.";
566
+ readonly recovery: "Retry with overwrite true or use obsidian_patch_note for in-place edits.";
567
+ }, {
525
568
  readonly reason: "path_forbidden";
569
+ readonly thrownBy: "service";
526
570
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
527
571
  readonly when: "The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes.";
528
572
  readonly recovery: "Use a path inside the configured write scope. The error data echoes the active scope.";
529
573
  }, {
530
574
  readonly reason: "note_missing";
575
+ readonly thrownBy: "service";
531
576
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
532
- readonly when: "Section append targets a path that does not resolve to an existing note (PATCH requires the file to exist).";
533
- readonly recovery: "Verify the path with obsidian_list_notes, or omit `section` to fall back to whole-file append (which creates the note if missing).";
577
+ readonly when: "Section replace targets a path that does not resolve to an existing note (PATCH requires the file to exist).";
578
+ readonly recovery: "Verify the path with obsidian_list_notes, or omit `section` to fall back to whole-file write (which creates the note when it is absent).";
534
579
  }, {
535
580
  readonly reason: "no_active_file";
581
+ readonly thrownBy: "service";
536
582
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
537
583
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
538
584
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
539
585
  }, {
540
586
  readonly reason: "periodic_unsupported";
587
+ readonly thrownBy: "service";
541
588
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
542
589
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
543
590
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
544
591
  }, {
545
592
  readonly reason: "periodic_not_found";
593
+ readonly thrownBy: "service";
546
594
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
547
595
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
548
596
  readonly recovery: "Create the periodic note first or pass an explicit path target.";
549
597
  }, {
550
598
  readonly reason: "periodic_disabled";
599
+ readonly thrownBy: "service";
551
600
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
552
601
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
553
602
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
554
603
  }, {
555
604
  readonly reason: "section_target_missing";
605
+ readonly thrownBy: "service";
556
606
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
557
607
  readonly when: "`section` was provided but the named heading/block/frontmatter field does not exist in the note.";
558
- readonly recovery: "Call obsidian_get_note with format document-map to discover available targets, or pass createTargetIfMissing: true to bring it into existence.";
608
+ readonly recovery: "Call obsidian_get_note with format document-map to discover available targets.";
559
609
  }, {
560
610
  readonly reason: "ambiguous_section";
611
+ readonly thrownBy: "service";
561
612
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
562
- readonly when: "A bare heading leaf name matches more than one heading in the note, so the append target is undetermined.";
563
- readonly recovery: "Retry with one of the full Parent::Child heading paths listed in `candidates` on the error data.";
564
- }, {
565
- readonly reason: "content_preexists";
566
- readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
567
- readonly when: "Section append where the supplied content already appears at the target — rejected to keep retries idempotent (the default for the section path).";
568
- readonly recovery: "Change the content to something not already present at the target, or use obsidian_patch_note with `patchOptions.applyIfContentPreexists: true` if a duplicate is intended.";
613
+ readonly when: "A bare heading leaf name matches more than one heading in the note, or the resolved full heading path occurs more than once, so the replacement target is undetermined.";
614
+ readonly recovery: "Retry with a distinct full Parent::Child heading path from `candidates` on the error data; when every candidate is the same path, rename the repeated headings first.";
569
615
  }, {
570
616
  readonly reason: "path_is_directory";
617
+ readonly thrownBy: "service";
571
618
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
572
619
  readonly when: "The supplied path names a folder rather than a note file.";
573
620
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then retry with the full path of one of the files it returns.";
574
621
  }, {
575
622
  readonly reason: "path_traversal";
623
+ readonly thrownBy: "service";
576
624
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
577
625
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
578
626
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -620,8 +668,9 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
620
668
  currentSizeInBytes: import("zod").ZodNumber;
621
669
  }, import("zod/v4/core").$strip>, readonly [{
622
670
  readonly reason: "path_forbidden";
671
+ readonly thrownBy: "service";
623
672
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
624
- readonly when: "The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes. (The pre-read also requires the path to be readable.)";
673
+ readonly when: "The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes. The pre-read also requires the path to be readable.";
625
674
  readonly recovery: "Use a path inside the configured write scope. The error data echoes the active scope.";
626
675
  }, {
627
676
  readonly reason: "regex_invalid";
@@ -636,40 +685,47 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
636
685
  }, {
637
686
  readonly reason: "frontmatter_invalid";
638
687
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
639
- readonly when: "A `scope` of \"frontmatter\" or \"both\" produced YAML that no longer parses as a mapping of properties. Nothing is written — the note keeps its original bytes. The check reads the rewritten YAML only, so a replacement that renames a key or changes a scalar's type while still parsing is not caught by it.";
688
+ readonly when: "A `scope` of \"frontmatter\" or \"both\" produced YAML that no longer parses as a mapping of properties, or that parses but cannot be re-emitted (an alias whose anchor the replacement removed). Nothing is written — the note keeps its original bytes. The check reads the rewritten YAML only, so a replacement that renames a key or changes a scalar's type while still parsing is not caught by it.";
640
689
  readonly recovery: "Narrow the search so it cannot match inside the YAML, or leave scope at \"body\" and edit the property with obsidian_manage_frontmatter.";
641
690
  }, {
642
691
  readonly reason: "note_missing";
692
+ readonly thrownBy: "service";
643
693
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
644
694
  readonly when: "The vault path does not resolve to an existing note.";
645
695
  readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
646
696
  }, {
647
697
  readonly reason: "no_active_file";
698
+ readonly thrownBy: "service";
648
699
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
649
700
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
650
701
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
651
702
  }, {
652
703
  readonly reason: "periodic_unsupported";
704
+ readonly thrownBy: "service";
653
705
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
654
706
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
655
707
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
656
708
  }, {
657
709
  readonly reason: "periodic_not_found";
710
+ readonly thrownBy: "service";
658
711
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
659
712
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
660
713
  readonly recovery: "Create the periodic note first or pass an explicit path target.";
661
714
  }, {
662
715
  readonly reason: "periodic_disabled";
716
+ readonly thrownBy: "service";
663
717
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
664
718
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
665
719
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
666
720
  }, {
667
721
  readonly reason: "path_is_directory";
722
+ readonly thrownBy: "service";
668
723
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
669
724
  readonly when: "The supplied path names a folder rather than a note file.";
670
725
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then retry with the full path of one of the files it returns.";
671
726
  }, {
672
727
  readonly reason: "path_traversal";
728
+ readonly thrownBy: "service";
673
729
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
674
730
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
675
731
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -721,6 +777,7 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
721
777
  }, import("zod/v4/core").$strip>], "operation">;
722
778
  }, import("zod/v4/core").$strip>, readonly [{
723
779
  readonly reason: "path_forbidden";
780
+ readonly thrownBy: "service";
724
781
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
725
782
  readonly when: "`get` requires the path to be readable; `set`/`delete` require it to be inside OBSIDIAN_WRITE_PATHS, with OBSIDIAN_READ_ONLY=false.";
726
783
  readonly recovery: "Use a path inside the configured scope. The error data echoes the active scope.";
@@ -729,38 +786,50 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
729
786
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
730
787
  readonly when: "`operation` is \"set\" but no `value` was supplied.";
731
788
  readonly recovery: "Pass `value` as any JSON-typed value: string, number, boolean, array, or object (e.g. `\"draft\"`, `42`, `true`, `[\"a\",\"b\"]`).";
789
+ }, {
790
+ readonly reason: "frontmatter_invalid";
791
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
792
+ readonly when: "`operation` is \"delete\" and the note's existing frontmatter does not parse as a mapping of properties, or removing the key would leave YAML that cannot be re-emitted (an alias whose anchor went with it). Nothing is written — the note keeps its original bytes.";
793
+ readonly recovery: "Read the block with obsidian_get_note (format \"content\"), repair the YAML between the `---` fences, then retry the delete.";
732
794
  }, {
733
795
  readonly reason: "note_missing";
796
+ readonly thrownBy: "service";
734
797
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
735
798
  readonly when: "The vault path does not resolve to an existing note.";
736
799
  readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
737
800
  }, {
738
801
  readonly reason: "no_active_file";
802
+ readonly thrownBy: "service";
739
803
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
740
804
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
741
805
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
742
806
  }, {
743
807
  readonly reason: "periodic_unsupported";
808
+ readonly thrownBy: "service";
744
809
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
745
810
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
746
811
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
747
812
  }, {
748
813
  readonly reason: "periodic_not_found";
814
+ readonly thrownBy: "service";
749
815
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
750
816
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
751
817
  readonly recovery: "Create the periodic note first or pass an explicit path target.";
752
818
  }, {
753
819
  readonly reason: "periodic_disabled";
820
+ readonly thrownBy: "service";
754
821
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
755
822
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
756
823
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
757
824
  }, {
758
825
  readonly reason: "path_is_directory";
826
+ readonly thrownBy: "service";
759
827
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
760
828
  readonly when: "The supplied path names a folder rather than a note file.";
761
829
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then retry with the full path of one of the files it returns.";
762
830
  }, {
763
831
  readonly reason: "path_traversal";
832
+ readonly thrownBy: "service";
764
833
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
765
834
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
766
835
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -820,6 +889,7 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
820
889
  }, import("zod/v4/core").$strip>], "operation">;
821
890
  }, import("zod/v4/core").$strip>, readonly [{
822
891
  readonly reason: "path_forbidden";
892
+ readonly thrownBy: "service";
823
893
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
824
894
  readonly when: "`list` requires the path to be readable; `add`/`remove` require it to be inside OBSIDIAN_WRITE_PATHS, with OBSIDIAN_READ_ONLY=false.";
825
895
  readonly recovery: "Use a path inside the configured scope. The error data echoes the active scope.";
@@ -828,38 +898,50 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
828
898
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
829
899
  readonly when: "`operation` is \"add\" or \"remove\" but `tags` was empty or omitted.";
830
900
  readonly recovery: "Pass a non-empty `tags` array (without `#`), e.g. `[\"draft\", \"wip\"]`.";
901
+ }, {
902
+ readonly reason: "frontmatter_invalid";
903
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
904
+ readonly when: "`location` reaches the frontmatter and the note's existing block does not parse as a mapping of properties, or the tag change would leave YAML that cannot be re-emitted. Nothing is written — the note keeps its original bytes, and under `location: \"both\"` the inline half is skipped too rather than applied on its own.";
905
+ readonly recovery: "Use `location: \"inline\"` to tag the body without touching the block, or repair the YAML between the `---` fences and retry.";
831
906
  }, {
832
907
  readonly reason: "note_missing";
908
+ readonly thrownBy: "service";
833
909
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
834
910
  readonly when: "The vault path does not resolve to an existing note.";
835
911
  readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
836
912
  }, {
837
913
  readonly reason: "no_active_file";
914
+ readonly thrownBy: "service";
838
915
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
839
916
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
840
917
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
841
918
  }, {
842
919
  readonly reason: "periodic_unsupported";
920
+ readonly thrownBy: "service";
843
921
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
844
922
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
845
923
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
846
924
  }, {
847
925
  readonly reason: "periodic_not_found";
926
+ readonly thrownBy: "service";
848
927
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
849
928
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
850
929
  readonly recovery: "Create the periodic note first or pass an explicit path target.";
851
930
  }, {
852
931
  readonly reason: "periodic_disabled";
932
+ readonly thrownBy: "service";
853
933
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
854
934
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
855
935
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
856
936
  }, {
857
937
  readonly reason: "path_is_directory";
938
+ readonly thrownBy: "service";
858
939
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
859
940
  readonly when: "The supplied path names a folder rather than a note file.";
860
941
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then retry with the full path of one of the files it returns.";
861
942
  }, {
862
943
  readonly reason: "path_traversal";
944
+ readonly thrownBy: "service";
863
945
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
864
946
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
865
947
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -887,6 +969,7 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
887
969
  currentSizeInBytes: import("zod").ZodNumber;
888
970
  }, import("zod/v4/core").$strip>, readonly [{
889
971
  readonly reason: "path_forbidden";
972
+ readonly thrownBy: "service";
890
973
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
891
974
  readonly when: "The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes.";
892
975
  readonly recovery: "Use a path inside the configured write scope. The error data echoes the active scope.";
@@ -894,39 +977,47 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
894
977
  readonly reason: "cancelled";
895
978
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidRequest;
896
979
  readonly when: "User declined, cancelled, or answered false to the confirmation request.";
980
+ readonly severity: "notice";
897
981
  readonly recovery: "Re-run the tool when the user is ready to confirm deletion.";
898
982
  }, {
899
983
  readonly reason: "note_missing";
984
+ readonly thrownBy: "service";
900
985
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
901
986
  readonly when: "The vault path does not resolve to an existing note.";
902
987
  readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
903
988
  }, {
904
989
  readonly reason: "no_active_file";
990
+ readonly thrownBy: "service";
905
991
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
906
992
  readonly when: "Target was `active` but no file is currently open in Obsidian.";
907
993
  readonly recovery: "Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.";
908
994
  }, {
909
995
  readonly reason: "periodic_unsupported";
996
+ readonly thrownBy: "service";
910
997
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
911
998
  readonly when: "Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.";
912
999
  readonly recovery: "Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.";
913
1000
  }, {
914
1001
  readonly reason: "periodic_not_found";
1002
+ readonly thrownBy: "service";
915
1003
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
916
1004
  readonly when: "Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.";
917
1005
  readonly recovery: "Pass an explicit path target — periodic notes must already exist.";
918
1006
  }, {
919
1007
  readonly reason: "periodic_disabled";
1008
+ readonly thrownBy: "service";
920
1009
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
921
1010
  readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
922
1011
  readonly recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.";
923
1012
  }, {
924
1013
  readonly reason: "path_is_directory";
1014
+ readonly thrownBy: "service";
925
1015
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
926
1016
  readonly when: "The supplied path names a folder rather than a note file. Deleting a folder is not offered — the upstream removes it and everything inside it in one unrecoverable step.";
927
1017
  readonly recovery: "Call obsidian_list_notes with this path to list the folder, then delete files one at a time by their full paths.";
928
1018
  }, {
929
1019
  readonly reason: "path_traversal";
1020
+ readonly thrownBy: "service";
930
1021
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
931
1022
  readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
932
1023
  readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
@@ -959,6 +1050,7 @@ export declare const commandToolDefinitions: (import("@cyanheads/mcp-ts-core").T
959
1050
  executed: import("zod").ZodBoolean;
960
1051
  }, import("zod/v4/core").$strip>, readonly [{
961
1052
  readonly reason: "command_unknown";
1053
+ readonly thrownBy: "service";
962
1054
  readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
963
1055
  readonly when: "The supplied `commandId` is not registered in Obsidian. Use `obsidian_list_commands` to discover valid IDs.";
964
1056
  readonly recovery: "Call obsidian_list_commands to discover the registered command IDs.";