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
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-3.5.3-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/obsidian-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/obsidian-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-3.5.5-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/obsidian-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/obsidian-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -21,12 +21,14 @@
21
21
 
22
22
  ---
23
23
 
24
- ## Tools
24
+ ## Overview
25
25
 
26
- Fourteen tools grouped by shape — readers fetch notes and metadata, writers create or surgically edit content, managers reconcile tags and frontmatter, and a guarded escape hatch dispatches Obsidian command-palette commands.
26
+ Read, write, search, and surgically edit Obsidian vault notes — sections, frontmatter, tags — over the Local REST API plugin, with folder-scoped read/write permissions built in. Runs as a stdio process or a local Streamable HTTP server.
27
27
 
28
- | Tool Name | Description |
29
- |:----------|:------------|
28
+ ### Tools
29
+
30
+ | Tool | Description |
31
+ |:---|:---|
30
32
  | `obsidian_get_note` | Read a note as raw content, full structured form (content + frontmatter + tags + stat, with optional outgoing links), structural document map, or a single section. |
31
33
  | `obsidian_list_notes` | List notes and subdirectories under a vault path. Recursive walk (default depth 2, max depth 20; 1000-entry cap) with optional `extension` and `nameRegex` filters. |
32
34
  | `obsidian_list_tags` | List vault tags with usage counts, including hierarchical parents. Ordered by count descending and capped at `limit` (default 200, max 10000), with the withheld remainder disclosed. Optional `nameRegex` and `minCount` narrow the set first. |
@@ -42,124 +44,161 @@ Fourteen tools grouped by shape — readers fetch notes and metadata, writers cr
42
44
  | `obsidian_open_in_ui` | Open a file in the Obsidian app UI, with `failIfMissing` and `newLeaf` toggles. |
43
45
  | `obsidian_execute_command` | Execute an Obsidian command-palette command by ID. **Opt-in via `OBSIDIAN_ENABLE_COMMANDS=true`.** |
44
46
 
45
- ### `obsidian_get_note`
47
+ ### Resources
46
48
 
47
- Read a note in one of four projections, addressed by vault path, the active file, or a periodic note (`daily`, `weekly`, `monthly`, `quarterly`, `yearly`).
49
+ | Resource | Description |
50
+ |:---|:---|
51
+ | `obsidian://vault/{+path}` | A note in the vault — content, frontmatter, tags, and file metadata. |
52
+ | `obsidian://tags` | All tags found across the vault, with usage counts (full snapshot). |
53
+ | `obsidian://status` | Server reachability, auth status, plugin/Obsidian version info, and registered API extensions. |
48
54
 
49
- - `format: "content"` — raw markdown body
50
- - `format: "full"` — content, frontmatter, tags, and file metadata; pass `includeLinks: true` to also parse outgoing wiki and markdown link references from the body (vault-internal only — external URLs are filtered)
51
- - `format: "document-map"` — catalog of headings, block references, and frontmatter fields
52
- - `format: "section"` — single heading/block/frontmatter section value (requires `section`); heading sections include the full subtree under that heading. The response echoes the locator the read resolved to in `sectionTarget` as a full `Parent::Child` path, and when a bare leaf name matches several headings it lists every colliding path in `candidates` — the read still returns the first match
55
+ Vault-note and tag data are also reachable via tools — `obsidian_get_note` for `obsidian://vault/{+path}`, `obsidian_list_tags` for `obsidian://tags` (count-ranked and capped, unlike the resource's raw snapshot). `obsidian://status` has no tool equivalent. Resources exist for clients that prefer attaching a note or vault snapshot to a conversation.
53
56
 
54
- Pair the document-map projection with `obsidian_patch_note` to discover edit targets before patching.
57
+ ## Capability reference
55
58
 
56
- ---
59
+ ### `obsidian_get_note` <sub>tool</sub>
57
60
 
58
- ### `obsidian_search_notes`
61
+ - `format: "content" | "full" | "document-map" | "section"` selects the projection; `full` accepts `includeLinks: true` for outgoing wiki/markdown links (vault-internal only — external URLs are filtered)
62
+ - Addressed by vault `path`, the `active` file, or a `periodic` note (`daily` / `weekly` / `monthly` / `quarterly` / `yearly`)
63
+ - Heading sections use `Parent::Child` syntax, and every `#`-style heading path the document map lists reads back as itself (setext headings, underlined with `===` or `---`, are not recognized); a bare leaf name matching several headings, or a full path that repeats in the note, returns the first match and lists every colliding path in `candidates`
64
+ - Forgiving `path` resolution: a case-mismatched path retries against the canonical filename, an ambiguous case match fails with `Conflict`, and a `NotFound` carries `Did you mean: …?` suggestions when near-matches exist
65
+ - Typed errors include `note_missing`, `path_forbidden`, `no_active_file`, `periodic_unsupported` / `periodic_disabled`, and `path_traversal`
59
66
 
60
- Up to three search modes selected by `mode`:
67
+ ---
61
68
 
62
- - `text` — substring match with surrounding context windows. `contextLength` controls characters of context per side of each match (default 100; bump it for more context per hit). Optional `pathPrefix` filter (text mode only — passing `pathPrefix` in any other mode is rejected with `path_prefix_invalid_mode`).
63
- - `jsonlogic` — JSONLogic tree evaluated against `path`, `content`, `frontmatter.<key>`, `tags`, and `stat.{ctime,mtime,size}`; custom `glob` and `regexp` operators, both taking `[PATTERN, VALUE]` — pattern first, then the field reference: `{"glob": ["Projects/*.md", {"var": "path"}]}`. The reverse order compiles the note's own field as the pattern: `glob` then matches nothing, and `regexp` fails outright on whatever the field parses as. This is also how backlinks are expressed, since there is no dedicated tool or upstream endpoint for them: `{"regexp": ["\\[\\[Target Note(\\||#|\\]\\])", {"var": "content"}]}` returns every note whose body wikilinks `Target Note`.
64
- - `omnisearch` — BM25-ranked search via the community [Omnisearch](https://github.com/scambier/obsidian-omnisearch) plugin. Supports quoted phrases, `-exclusion`, `path:` / `ext:` filters, typo tolerance, and PDF + OCR coverage (via [Text Extractor](https://github.com/scambier/obsidian-text-extractor)). Only present in the mode enum when the plugin's HTTP server is reachable at startup; the upstream hard-caps results at 50 — narrow the query to surface more (the response carries `truncated: true` when the cap was likely hit).
69
+ ### `obsidian_list_notes` <sub>tool</sub>
65
70
 
66
- Results paginate via opaque cursors per the [MCP 2025-11-25 spec](https://modelcontextprotocol.io/specification/2025-11-25/utils/pagination): omit `cursor` for the first page, then pass `nextCursor` from the prior response. Every result carries `totalCount` (post-path-policy, pre-pagination); `nextCursor` is omitted on the last page. Text-mode hits are additionally clipped per file at `maxMatchesPerHit` (default 10) so a single match-heavy note can't blow the response budget — clipped hits carry `truncated: true` and `totalMatches`.
71
+ - Recursive walk from `path` (default vault root); `depth` 1–20 (default 2 = target plus immediate children)
72
+ - Optional `extension` and `nameRegex` (≤256 chars, no nested quantifiers) filters; a directory failing `nameRegex` is skipped without recursing into it
73
+ - Hard cap of 1000 entries per call — `excluded.reason: "entry_cap"` signals a truncated walk; narrow `path` or the filters to see the rest
74
+ - Per-directory `truncated: true` marks entries cut off by the depth limit or by path policy
67
75
 
68
76
  ---
69
77
 
70
- ### `obsidian_write_note`
78
+ ### `obsidian_list_tags` <sub>tool</sub>
71
79
 
72
- Create or surgically replace, with a protective default against accidental whole-file overwrites.
80
+ - Vault-wide tag counts, including hierarchical parents (`work/tasks` contributes to both `work` and `work/tasks`)
81
+ - Ordered by count descending, capped at `limit` (default 200, max 10000); optional `nameRegex` and `minCount` narrow the candidate set before ranking
82
+ - Reports `truncated` / `shown` / `cap` when the limit withheld results
83
+ - Not narrowed by `OBSIDIAN_READ_PATHS` — tag names (never note contents) can surface from outside the read scope
73
84
 
74
- - Without `section` — full-file `PUT`. **Refuses to clobber an existing file** unless `overwrite: true` is set. The `file_exists` (`Conflict`) error suggests `obsidian_patch_note` / `obsidian_append_to_note` / `obsidian_replace_in_note` for in-place edits.
75
- - With `section` — `PATCH`-with-replace against the named heading/block/frontmatter field, leaving the rest of the file untouched. The `overwrite` flag is ignored in section mode.
85
+ ---
76
86
 
77
- The output reports `created: true` when the call brought a new file into existence; `false` when it replaced an existing one or targeted a section. Every mutating tool also returns `previousSizeInBytes` and `currentSizeInBytes` so an agent can spot accidental clobbers, unexpected upstream behavior, or a typo path that landed at the wrong file.
87
+ ### `obsidian_list_commands` <sub>tool</sub>
88
+
89
+ - Lists Obsidian command-palette IDs and display names; optional `nameRegex` filters on display name
90
+ - **Opt-in via `OBSIDIAN_ENABLE_COMMANDS=true`** — absent from `tools/list` when unset
91
+ - Discovery partner for `obsidian_execute_command`
78
92
 
79
93
  ---
80
94
 
81
- ### `obsidian_append_to_note`
95
+ ### `obsidian_search_notes` <sub>tool</sub>
96
+
97
+ - `mode: "text" | "jsonlogic"` always; `"omnisearch"` is added to the schema only when the Omnisearch plugin's HTTP server is reachable at startup (restart to re-probe)
98
+ - `text` — whitespace-split tokens, all required, each matched case-insensitively as a substring (quotes are literal, so there is no phrase operator), with `contextLength`-sized context windows (default 100) and an optional `pathPrefix`; tokens within 2 × `contextLength` of each other, such as a phrase's words, share one match location; `jsonlogic` — a JSONLogic tree with `var` paths into `path` / `content` / `frontmatter.<key>` / `tags` / `stat.{ctime,mtime,size}`, plus `glob` / `regexp` operators taking `[PATTERN, VALUE]`; `omnisearch` — BM25-ranked, quoted phrases, `-exclusion`, `path:` / `ext:` filters, typo tolerance, PDF/OCR via Text Extractor, hard-capped at 50 upstream hits (`truncated: true` when likely hit)
99
+ - Cursor pagination — omit `cursor` for page one, pass `nextCursor` from the prior response; text-mode hits additionally clip to `maxMatchesPerHit` match locations (default 10), flagged with `truncated` / `totalMatches`
100
+ - No dedicated backlinks tool — express "what links here" via `jsonlogic`: `{"regexp": ["\\[\\[Target Note(\\||#|\\]\\])", {"var": "content"}]}`
82
101
 
83
- A combined upsert + section-append primitive that mirrors the upstream Local REST API behavior:
102
+ ---
84
103
 
85
- - Without `section` — `POST` to `/vault/{path}`. Appends when the file exists, **creates the file with your content as the entire body when it doesn't.** The output's `created: true` flags the second branch so the agent can notice when a typo path or a not-yet-created daily note silently turned into a brand-new file.
86
- - With `section` — `PATCH`-with-append against the named heading, block reference, or frontmatter field. The file must exist (PATCH preflight throws `note_missing` otherwise). Pass `createTargetIfMissing: true` to bring the section itself into existence inside an existing file. Block-reference targets concatenate adjacent to the block line without a separator — include a leading newline in `content` if you want one.
104
+ ### `obsidian_write_note` <sub>tool</sub>
87
105
 
88
- `previousSizeInBytes` is `0` on the upsert-create branch and the actual file size otherwise; `currentSizeInBytes` is the post-write size read from the upstream after the operation. Compare deltas against `Buffer.byteLength(content)` to detect auto-newline injection or concurrent writers.
106
+ - Without `section` — full-file write; refuses to clobber an existing note unless `overwrite: true` (`file_exists` conflict otherwise, naming the surgical-edit tools as the alternative)
107
+ - With `section` — `PATCH`-with-replace against a heading/block/frontmatter target, leaving the rest of the file untouched (`overwrite` is ignored); a bare heading leaf shared by several headings fails with `ambiguous_section` unless one of them has no parent heading, which the write then targets, and a full heading path that repeats in the note fails the same way
108
+ - Output reports `created`, plus `previousSizeInBytes` / `currentSizeInBytes` on every call to spot an accidental clobber or a mistyped path
89
109
 
90
110
  ---
91
111
 
92
- ### `obsidian_patch_note`
112
+ ### `obsidian_append_to_note` <sub>tool</sub>
93
113
 
94
- Surgical edits at a single document target.
114
+ - Without `section` — appends to an existing file, or creates it with the given content as the whole body (`created: true` flags the second case)
115
+ - With `section` — appends to a heading/block/frontmatter target; the file must already exist, and `createTargetIfMissing: true` brings the section itself into existence
116
+ - Block-reference targets concatenate with no separator — include a leading newline in `content` for one
117
+ - `previousSizeInBytes` / `currentSizeInBytes` bracket every call for drift detection
95
118
 
96
- - `operation: "append"` adds after the section
97
- - `operation: "prepend"` adds before the section
98
- - `operation: "replace"` swaps it out
99
- - Targets: heading path, block reference ID, or frontmatter field
119
+ ---
100
120
 
101
- Heading targets accept either the full `Parent::Child` path or a bare leaf name. A bare leaf that matches exactly one heading is expanded to its full path before the write, and the response echoes the locator the edit landed on; a leaf matching several headings is rejected with `ambiguous_section`, whose error data lists the candidate paths. The same resolution applies to `obsidian_write_note` and `obsidian_append_to_note` with `section`.
121
+ ### `obsidian_patch_note` <sub>tool</sub>
102
122
 
103
- Use `obsidian_get_note` with `format: "document-map"` to discover what targets exist before patching.
123
+ - `operation: "append" | "prepend" | "replace"` against one heading, block reference, or frontmatter field per call
124
+ - Heading targets accept the full `Parent::Child` path or a bare leaf name; a leaf matching several headings fails with `ambiguous_section` and lists the candidates, unless one of them has no parent heading, which the patch then targets; a full path that repeats in the note fails with `ambiguous_section` too
125
+ - `patchOptions`: `createTargetIfMissing`, `applyIfContentPreexists` (idempotency guard — otherwise `content_preexists`), `trimTargetWhitespace`
104
126
 
105
127
  ---
106
128
 
107
- ### `obsidian_replace_in_note`
129
+ ### `obsidian_replace_in_note` <sub>tool</sub>
108
130
 
109
- Search-replace for edits that don't fit `obsidian_patch_note`'s structural targets. The note is fetched, replacements are applied sequentially (each sees the previous output), and the result is written back in a single `PUT`.
131
+ - One or more `replacements`, applied in array order, each over the previous one's output
132
+ - `scope: "body"` (default, frontmatter left byte-identical) | `"frontmatter"` | `"both"`; frontmatter/both re-parse the rewritten YAML afterward and write nothing if it breaks (`frontmatter_invalid`)
133
+ - Per-replacement options: `useRegex` (≤1024 chars, no nested quantifiers), `caseSensitive`, `wholeWord` (`\b…\b` in both modes), `flexibleWhitespace` (literal mode only), `replaceAll` (default `true`)
134
+ - `perReplacement[]` reports `bodyCount` / `frontmatterCount` per entry; `totalReplacements` sums them
110
135
 
111
- `scope` selects what the replacements run over:
136
+ ---
112
137
 
113
- - `body` (default) — the text after the YAML frontmatter block. The block is re-attached from the original bytes, so it comes back byte-identical.
114
- - `frontmatter` — only the YAML between the `---` fences. The fences themselves are never matched.
115
- - `both` — each replacement runs over the frontmatter and then the body; `perReplacement[]` reports `bodyCount` and `frontmatterCount` separately.
138
+ ### `obsidian_manage_frontmatter` <sub>tool</sub>
116
139
 
117
- With the frontmatter in scope, the rewritten YAML is re-parsed before anything is written: if it no longer parses as a mapping of properties, the call fails with `frontmatter_invalid` and the note keeps its original bytes. That check catches YAML that breaks — an unquoted `:` in a scalar, a list marker rewritten into an alias, a stray quote. It cannot catch an edit that stays well-formed while meaning something else, such as a substring collision that renames a key or a replacement that drops a scalar's quotes and changes its type. Prefer `obsidian_manage_frontmatter` for typed edits to a single property.
140
+ - `operation: "get" | "set" | "delete"` on a single frontmatter `key`; `set` requires a JSON-typed `value` (string, number, boolean, array, or object)
141
+ - `get` needs read access; `set` / `delete` need the path inside `OBSIDIAN_WRITE_PATHS` with `OBSIDIAN_READ_ONLY=false`
142
+ - `set` / `delete` return the full `frontmatter` after the change plus `previousSizeInBytes` / `currentSizeInBytes`
118
143
 
119
- Per-replacement options:
144
+ ---
120
145
 
121
- - `useRegex` — treat `search` as an ECMAScript regex. With `useRegex: true`, the replacement honors `$1` / `$&` capture-group references.
122
- - `caseSensitive` — when `false`, match case-insensitively
123
- - `wholeWord` — wrap the pattern in `\b…\b`; works in both literal and regex modes
124
- - `flexibleWhitespace` — substitute any run of whitespace in `search` with `\s+`. Literal mode only — has no effect when `useRegex: true` (express it directly).
125
- - `replaceAll` — when `false`, only the first match is replaced. Under `scope: 'both'` that one substitution goes to the frontmatter when it matches there, and to the body otherwise.
146
+ ### `obsidian_manage_tags` <sub>tool</sub>
126
147
 
127
- Literal mode preserves `$1` / `$&` in the replacement verbatim — only `useRegex: true` expands capture-group references.
148
+ - `operation: "add" | "remove" | "list"`; `location: "frontmatter"` (default, canonical `tags:` array) | `"inline"` (body `#tag`, `add` appends at end-of-file) | `"both"` (reconciles both)
149
+ - Inline detection skips fenced/inline code spans, link spans (`[[...]]`, `[text](...)`, `[text][ref]`), HTML comments, and math (`$…$`, `$$…$$`), so a heading anchor or wikilink alias is never mistaken for a tag; `%% … %%` comments are still read, as Obsidian reads them
150
+ - Inline tags follow Obsidian's grammar: a tag starts at line start, after whitespace, or right after markup such as `**`, `==`, `<br>`, or a `\`-escape (`**#x**` is a tag; `(#x`, `.#x`, `a *#x`, and `\#x` are not) and runs through letters and digits in any script, emoji, `_`, `-`, and `/`, with at least one character that is not an ASCII digit (`#1990s`, `#café`, `#日本語`, and `#✅done` are tags; `#1984` is not)
151
+ - `add` / `remove` report `applied` vs. `skipped` tags plus the full `tags` set after the change; `list` ignores the input `tags` array
128
152
 
129
153
  ---
130
154
 
131
- ### `obsidian_manage_tags`
132
-
133
- Add, remove, or list tags on a note. Operates on one of two representations, defaulting to the canonical Obsidian frontmatter location:
155
+ ### `obsidian_delete_note` <sub>tool</sub>
134
156
 
135
- - `location: 'frontmatter'` (default) — only the frontmatter `tags:` array; the note body is left untouched
136
- - `location: 'inline'` — only inline `#tag` syntax in the body; `add` appends `#tag` at end-of-file
137
- - `location: 'both'` — opt-in reconciliation across both representations
157
+ - Always asks for confirmation first — the initial call returns an elicitation request naming the file's byte size, and is retried with the answer; declining fails with `cancelled` and issues no `DELETE`
158
+ - No API-level undo — recovery requires Obsidian's local trash
159
+ - Requires an MCP client that can serve an elicitation round-trip; every other tool works without one
138
160
 
139
- `add` ensures the tag is present in the requested location(s); `remove` strips it; `list` ignores the input `tags` array.
161
+ ---
140
162
 
141
- Inline `#tag` detection skips code spans (fenced and inline), link spans (`[[...]]`, `[text](...)`, `[text][ref]`) — so a heading anchor, block anchor, or wikilink alias is never read as a tag or rewritten by a removal — and a hash escaped as `\#`. A `#` inside an HTML comment or a math span is still counted. `list` and `remove` run the same detection, so what `list` reports is what `remove` can reach.
163
+ ### `obsidian_open_in_ui` <sub>tool</sub>
142
164
 
143
- Inline mode reads and writes the note body only — a `#` inside a YAML scalar is frontmatter, so it is neither listed as an inline tag nor rewritten by a removal. Removing an inline tag takes exactly one adjacent horizontal space with it — the one before the tag, or the one after when no space precedes it; every other byte survives, including nested list indentation, four-space indented code blocks, trailing two-space hard line breaks, and table cell padding.
165
+ - `failIfMissing` (default `true`) controls open-vs-create: opening an existing file needs read access, opening a missing one (with `failIfMissing: false`) creates it and needs write access
166
+ - `newLeaf` opens in a split pane instead of the active one
167
+ - Same forgiving path resolution as `obsidian_get_note` (case fallback, `Did you mean` suggestions); `obsidian_delete_note` deliberately doesn't get it — a destructive op never silently rewrites its target
168
+ - Output reports `createdIfMissing` so the caller can tell which branch ran
144
169
 
145
170
  ---
146
171
 
147
- ### `obsidian_delete_note`
172
+ ### `obsidian_execute_command` <sub>tool</sub>
148
173
 
149
- Permanently delete a note. The first call answers with a confirmation request rather than a deletion — the prompt includes the file's byte size, so the destructive blast radius is visible before the user confirms — and the tool is retried with the answer. Declining or cancelling fails the call with `cancelled` and issues no `DELETE`; the `destructiveHint` annotation also surfaces the operation in the host's approval flow. The output reports `previousSizeInBytes` (size at the moment of deletion) and `currentSizeInBytes: 0`.
150
-
151
- The confirmation is not optional and has no fallback path: a client that cannot serve the input round-trip cannot complete a delete. Every other tool is unaffected.
174
+ - Dispatches an Obsidian command-palette command by `commandId` (discover via `obsidian_list_commands`); runs with the same authority as a keyboard invocation
175
+ - **Opt-in via `OBSIDIAN_ENABLE_COMMANDS=true`** — absent from `tools/list` when unset
176
+ - Behavior is command-dependent — some are destructive (delete file, close vault), some open UI
152
177
 
153
178
  ---
154
179
 
155
- ### `obsidian_execute_command`
180
+ ### `obsidian://vault/{+path}` <sub>resource</sub>
181
+
182
+ - The `{+path}` segment captures everything after `/vault/`, including slashes
183
+ - Paths may be sent literally or percent-encoded — `Folder/Test Note.md` and `Folder/Test%20Note.md` resolve to the same note, as do non-ASCII names and a bare `%`
184
+ - Returns the same shape as `obsidian_get_note` with `format: "full"` — content, frontmatter, tags, stat
185
+ - Gated by `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` like the tool equivalent
156
186
 
157
- Dispatch an Obsidian command-palette command by ID (discoverable via `obsidian_list_commands`). Behavior is command-dependent — some commands open UI, others delete files or close the vault.
187
+ ---
188
+
189
+ ### `obsidian://tags` <sub>resource</sub>
158
190
 
159
- **Off by default.** When `OBSIDIAN_ENABLE_COMMANDS` is unset, both `obsidian_execute_command` and its discovery partner `obsidian_list_commands` are wrapped with `disabledTool()` — absent from `tools/list` (the LLM can't invoke them) but still visible in the operator-facing manifest with a hint to enable them.
191
+ - Full snapshot of the upstream `/tags/` payload — unsorted, uncapped, includes hierarchical parents
192
+ - Not a mirror of `obsidian_list_tags`: no count-descending order, no `limit` / `nameRegex` / `minCount`
160
193
 
161
194
  ---
162
195
 
196
+ ### `obsidian://status` <sub>resource</sub>
197
+
198
+ - Reachability, plugin version, `authenticated` (whether the configured `OBSIDIAN_API_KEY` was accepted), and plugin manifest info
199
+ - `apiExtensions[]` lists registered plugin extensions — check for `local-rest-api-periodic-notes` before relying on `periodic` targets on plugin v5.0.2 and later
200
+ - Still reports reachability when the API key is misconfigured; only `authenticated` reflects the key's validity
201
+
163
202
  ## Path policy (folder-scoped permissions)
164
203
 
165
204
  Three optional env vars gate which vault paths each tool can target. **Default unset = full vault** for both reads and writes — backwards compatible.
@@ -183,41 +222,24 @@ Denies are typed `path_forbidden` (JSON-RPC code `Forbidden`) with the active sc
183
222
 
184
223
  The startup banner logs the active scope so operators can verify their config at boot.
185
224
 
186
- ---
187
-
188
- ## Resources
189
-
190
- | Type | URI | Description |
191
- |:---|:---|:---|
192
- | Resource | `obsidian://vault/{+path}` | A note in the vault — content, frontmatter, tags, and file metadata. |
193
- | Resource | `obsidian://tags` | All tags found across the vault, with usage counts. |
194
- | Resource | `obsidian://status` | Server reachability, auth status, plugin/Obsidian version info, and the plugin manifest. |
195
-
196
- All resource data is also reachable via tools — `obsidian_get_note` for `obsidian://vault/{+path}`, `obsidian_list_tags` for `obsidian://tags`. Resources exist for clients that prefer attaching a specific note or vault snapshot to a conversation. The tag pair is not a mirror: `obsidian://tags` keeps snapshot semantics and returns the upstream payload whole and unsorted, while `obsidian_list_tags` orders by count and caps.
197
-
198
225
  ## Features
199
226
 
200
- Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
201
-
202
- - Declarative tool and resource definitions — single file per primitive, framework handles registration and validation
203
- - Unified error handling — handlers throw, framework catches, classifies, and formats. Tools advertise their failure surface via typed `errors[]` contracts.
204
- - Server-level `instructions` on `initialize` — surfaces deployment-specific orientation (active path policy, read-only mode, command-palette toggle) to spec-compliant clients alongside the static tool/resource catalog
205
- - Pluggable auth on the HTTP transport: `none`, `jwt`, `oauth`
206
- - Structured logging with optional OpenTelemetry tracing
207
- - STDIO and Streamable HTTP transports
208
-
209
- The server itself is stateless — every tool call hits the Local REST API directly. The framework's storage backends, request-state KV, and progress streams aren't used here; Obsidian is single-vault and there's nothing to persist between calls.
227
+ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
210
228
 
211
229
  Obsidian-specific:
212
230
 
213
231
  - Wraps the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin — typed client, deterministic error mapping
214
232
  - Section-aware editing across headings, block references, and frontmatter fields via `PATCH`-with-target operations
215
- - Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax (skipping code spans, link spans, and hashes escaped as `\#`)
216
- - Search across up to three modes: text, JSONLogic, and (when the plugin is reachable) BM25-ranked Omnisearch — cursor-paginated per the MCP 2025-11-25 spec, with per-file match clipping in text mode
217
- - Required human-in-the-loop confirmation for destructive deletes — a multi-round-trip `input_required` round served on both protocol revisions, with no unconfirmed path through the tool
218
- - Folder-scoped read/write permissions via `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` and a global `OBSIDIAN_READ_ONLY` kill switch — denies are typed `path_forbidden` with the active scope echoed back in the error data
219
- - Opt-in command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`) — registered only when `OBSIDIAN_ENABLE_COMMANDS=true`
220
- - Forgiving path resolution on `obsidian_get_note` and `obsidian_open_in_ui` — silently retries case-mismatched paths against the canonical filename, throws `Conflict` on ambiguous case matches, and enriches `NotFound` with `Did you mean: …?` suggestions when only near-matches exist. `obsidian_delete_note` is deliberately excluded — a destructive op shouldn't silently rewrite the target path.
233
+ - Search across three modes — text, JSONLogic, and (when reachable) BM25-ranked Omnisearch — cursor-paginated per the MCP 2025-11-25 spec
234
+ - Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax
235
+ - Folder-scoped read/write permissions via `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` and a global `OBSIDIAN_READ_ONLY` kill switch; opt-in command-palette pair gated by `OBSIDIAN_ENABLE_COMMANDS`. Server-level `instructions` on `initialize` report the active policy to the caller
236
+
237
+ Agent-friendly output:
238
+
239
+ - Recovery-guided errors — every declared failure carries a `reason`, a JSON-RPC code, and a `recovery.hint` written for that case, so a rejection names what to do next instead of only what broke
240
+ - Size-delta self-correction — every mutating tool returns `previousSizeInBytes` / `currentSizeInBytes`, so a caller can spot an accidental clobber or unexpected upstream behavior without a follow-up read
241
+ - Ambiguity surfaced structurally — a heading leaf name shared by several headings returns `candidates` instead of silently picking one; tag operations report `applied` vs. `skipped` so a caller sees exactly what changed
242
+ - Discriminated output contracts — `format` on `obsidian_get_note`, `operation` on `obsidian_manage_frontmatter` and `obsidian_manage_tags`, `mode` on `obsidian_search_notes` — callers branch on typed fields instead of parsing text
221
243
 
222
244
  ## Getting started
223
245
 
@@ -259,6 +281,28 @@ Or with npx (no Bun required):
259
281
  }
260
282
  ```
261
283
 
284
+ Or with Docker:
285
+
286
+ ```json
287
+ {
288
+ "mcpServers": {
289
+ "obsidian-mcp-server": {
290
+ "type": "stdio",
291
+ "command": "docker",
292
+ "args": [
293
+ "run", "-i", "--rm",
294
+ "-e", "MCP_TRANSPORT_TYPE=stdio",
295
+ "-e", "MCP_LOG_LEVEL=info",
296
+ "-e", "OBSIDIAN_API_KEY=your-local-rest-api-key",
297
+ "ghcr.io/cyanheads/obsidian-mcp-server:latest"
298
+ ]
299
+ }
300
+ }
301
+ }
302
+ ```
303
+
304
+ The default `OBSIDIAN_BASE_URL` (`http://127.0.0.1:27123`) points at the container's own loopback, not your host — add `-e OBSIDIAN_BASE_URL=http://host.docker.internal:27123` (Docker Desktop) or run with `--network host` (Linux) so the container can reach the plugin.
305
+
262
306
  For Streamable HTTP, set the transport and start the server. Inline env vars work for one-off runs; for repeated use, copy values into `.env` (see [`.env.example`](./.env.example)) and run `bun run start:http`.
263
307
 
264
308
  ```sh
@@ -272,7 +316,7 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
272
316
  - The [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin, **v4.0.0 through v5.x**, installed and enabled in your vault. Generate an API key in **Settings → Community Plugins → Local REST API** and copy it into `OBSIDIAN_API_KEY`. Plugin v6.0 removes the markdown-patch 1.x wire format this server pins for section-targeted writes and the document map.
273
317
  - Periodic-note targets (`target: { "type": "periodic" }`) work across that whole range: natively on plugin **v5.0.1 and earlier**, and on **v5.0.2 and later** — which moved the `/periodic/` routes out of the plugin — once the companion [periodic-notes API extension](https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes) is installed. Without that extension on v5.0.2+, periodic targets fail with a `periodic_unsupported` error naming it; `obsidian://status` lists the registered extensions if you want to check first. Every other target type is unaffected.
274
318
  - An MCP client that can answer an input request (elicitation). `obsidian_delete_note` always asks for confirmation before deleting, so a client without that support can read and write notes but cannot delete one.
275
- - This server defaults to `http://127.0.0.1:27123` for simplicity. Enable **"Non-encrypted (HTTP) Server"** in the plugin settings to use it. To use the always-on HTTPS port instead, set `OBSIDIAN_BASE_URL=https://127.0.0.1:27124`; the plugin's self-signed cert is handled by `OBSIDIAN_VERIFY_SSL=false` (the default).
319
+ - This server defaults to `http://127.0.0.1:27123` for simplicity. Enable **"Non-encrypted (HTTP) Server"** in the plugin settings to use it. To use the always-on HTTPS port instead, set `OBSIDIAN_BASE_URL=https://127.0.0.1:27124`; the plugin's self-signed cert is handled by `OBSIDIAN_VERIFY_SSL=false` (the default), which relaxes verification for this server's requests to that endpoint only.
276
320
 
277
321
  ### Installation
278
322
 
@@ -306,8 +350,8 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
306
350
  | Variable | Description | Default |
307
351
  |:---------|:------------|:--------|
308
352
  | `OBSIDIAN_API_KEY` | **Required.** Bearer token for the Obsidian Local REST API plugin. | — |
309
- | `OBSIDIAN_BASE_URL` | Base URL of the Local REST API plugin. Use `https://127.0.0.1:27124` for the always-on HTTPS port (self-signed cert). | `http://127.0.0.1:27123` |
310
- | `OBSIDIAN_VERIFY_SSL` | Verify the TLS certificate. Default `false` because the plugin uses a self-signed cert. On Node, the dispatcher's `rejectUnauthorized` option handles this without any process-wide change. On Bun, the runtime ignores that option, so the service additionally sets `NODE_TLS_REJECT_UNAUTHORIZED=0` — that fallback is scoped to Bun only. | `false` |
353
+ | `OBSIDIAN_BASE_URL` | Base URL of the Local REST API plugin. Use `https://127.0.0.1:27124` for the always-on HTTPS port (self-signed cert). A trailing slash is stripped at startup. When nothing answers there (Obsidian closed, plugin disabled, wrong host or port), calls fail with `obsidian_unreachable` — a `GET`, `PUT`, or `DELETE` after its retries, any other request on the first attempt. | `http://127.0.0.1:27123` |
354
+ | `OBSIDIAN_VERIFY_SSL` | Verify the TLS certificate. Default `false` because the plugin uses a self-signed cert. The relaxation is applied per request, to an `https:` `OBSIDIAN_BASE_URL` only — every other HTTPS connection the process makes still verifies normally, on both Bun and Node. With `true`, a certificate the runtime does not trust fails every call on its first attempt with `certificate_rejected`. | `false` |
311
355
  | `OBSIDIAN_REQUEST_TIMEOUT_MS` | Per-request timeout in milliseconds. | `30000` |
312
356
  | `OBSIDIAN_ENABLE_COMMANDS` | Opt-in flag for the command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`). Off by default — Obsidian commands are opaque and can be destructive. | `false` |
313
357
  | `OBSIDIAN_READ_PATHS` | Comma-separated vault-relative folder allowlist for read operations. Prefix-based with implicit recursion; case-insensitive; trailing slashes normalized. Unset = full vault. Write paths are implicitly readable. | unset |
@@ -318,7 +362,7 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
318
362
  | `MCP_HTTP_HOST` | Host for the HTTP server. | `127.0.0.1` |
319
363
  | `MCP_HTTP_PORT` | Port for the HTTP server. | `3010` |
320
364
  | `MCP_HTTP_ENDPOINT_PATH` | Endpoint path for the JSON-RPC handler. | `/mcp` |
321
- | `MCP_SESSION_MODE` | Session handling for the HTTP transport: `stateless`, `stateful`, or `auto`. Pinned to `stateful` — `obsidian_delete_note` confirms via an elicitation round, which `stateless` disables. | `stateful` |
365
+ | `MCP_SESSION_MODE` | Session handling for the HTTP transport: `stateless`, `stateful`, or `auto`. Defaults to `stateful` here — `obsidian_delete_note` confirms via an elicitation round, and under `stateless` a 2025-era client's round is refused (`client_capability_missing`). | `stateful` |
322
366
  | `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments (landing page, Server Card, RFC 9728 metadata). | unset |
323
367
  | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
324
368
  | `MCP_AUTH_SECRET_KEY` | **Required when `MCP_AUTH_MODE=jwt`.** ≥32-char shared secret used to verify incoming JWTs. | — |
@@ -360,7 +404,7 @@ docker build -t obsidian-mcp-server .
360
404
  docker run --rm -e OBSIDIAN_API_KEY=your-key -p 3010:3010 obsidian-mcp-server
361
405
  ```
362
406
 
363
- The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/obsidian-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
407
+ The Dockerfile defaults to HTTP transport, stateful session mode (required for the `obsidian_delete_note` confirmation round), and logs to `/var/log/obsidian-mcp-server`. Point `OBSIDIAN_BASE_URL` at `http://host.docker.internal:27123` (Docker Desktop) or run with `--network host` (Linux) so the container reaches the plugin on your host. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
364
408
 
365
409
  The image binds to `0.0.0.0` inside the container (required for Docker port mapping). For any deployment reachable beyond your own machine, set `MCP_AUTH_MODE=jwt` (with `MCP_AUTH_SECRET_KEY`) or `oauth` — otherwise the listener forwards your `OBSIDIAN_API_KEY` to the vault on behalf of every caller.
366
410
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- summary: "Bun engines floor rises to >=1.4.0 for mcp-ts-core 0.13.0; the development skill tree moves to framework-skills/ so installing the plugin no longer loads it into the agent; empty or placeholder ${...} env values now read as unset, and plugin manifests wire OBSIDIAN_* through userConfig/env_vars instead of empty-string placeholders."
2
+ summary: "The .mcpb bundle now passes install-time settings instead of literal ${OBSIDIAN_*} text, and plugin installs no longer blank out exported OBSIDIAN_* vars; empty or placeholder ${...} env values read as unset; dev skills move to framework-skills/ so plugin installs stop loading them; Bun floor rises to >=1.4.0."
3
3
  breaking: false
4
4
  security: false
5
5
  ---
@@ -8,14 +8,15 @@ security: false
8
8
 
9
9
  ## Changed
10
10
 
11
- - **Bun engines floor raised to `>=1.4.0`** — required by mcp-ts-core 0.13.0.
11
+ - **Bun engines floor raised to `>=1.4.0`** — required since mcp-ts-core 0.12.9.
12
12
  - **Development skills moved from `skills/` to `framework-skills/`** — Claude Code and Codex auto-load a plugin's root `skills/`, so installing this server's plugin no longer loads its development skills into the installing agent ([cyanheads/mcp-ts-core#428](https://github.com/cyanheads/mcp-ts-core/issues/428)).
13
13
 
14
14
  ## Fixed
15
15
 
16
16
  - **An empty or whole-value `${...}` env value now reads as unset** — `OBSIDIAN_REQUEST_TIMEOUT_MS=''` starts with the default timeout and `OBSIDIAN_API_KEY='${OBSIDIAN_API_KEY}'` fails at startup as a missing key rather than as a format error; the server's own redundant empty-string guards on `baseUrl`/`omnisearchUrl` were removed as a result ([cyanheads/mcp-ts-core#427](https://github.com/cyanheads/mcp-ts-core/issues/427)).
17
17
  - **A cancelled single-request HTTP POST's SSE stream now closes immediately**, instead of waiting for keep-alive or session expiry ([cyanheads/mcp-ts-core#401](https://github.com/cyanheads/mcp-ts-core/issues/401)).
18
- - **Plugin manifests no longer ship empty-string env placeholders** — `manifest.json` and `.claude-plugin/plugin.json` set `OBSIDIAN_API_KEY`/`OBSIDIAN_BASE_URL` to `""`, which overrides a user's exported value with an unset one; both now source them via `userConfig` + `${user_config.<key>}`, and `.codex-plugin/mcp.json` lists them in `env_vars`.
18
+ - **The `.mcpb` bundle now passes the settings entered at install to the server** — `manifest.json`'s `mcp_config` referenced `${OBSIDIAN_*}`, which the MCPB host does not substitute, so the server received the literal placeholder text; every option is now wired as `${user_config.<key>}`.
19
+ - **Plugin installs no longer blank out exported env vars** — `.claude-plugin/plugin.json` and `.codex-plugin/mcp.json` set `OBSIDIAN_API_KEY`/`OBSIDIAN_BASE_URL` to `""`, replacing a value the user had exported; the Claude Code plugin now declares them as `userConfig` options, and the Codex plugin forwards them through `env_vars`.
19
20
 
20
21
  ## Dependencies
21
22
 
@@ -0,0 +1,42 @@
1
+ ---
2
+ summary: "TLS certificate verification is now scoped to each Obsidian request instead of relaxed process-wide on Bun, frontmatter/tag edits refuse to write rather than destroy content on unsafe YAML, and mcp-ts-core moves to ^0.13.6."
3
+ breaking: false
4
+ security: true
5
+ ---
6
+
7
+ # 3.5.4 — 2026-09-19
8
+
9
+ ## Added
10
+
11
+ - **`createApp` declares `sessionMode: 'stateful'` and a `teardown` hook** ([#120](https://github.com/cyanheads/obsidian-mcp-server/issues/120)) — `obsidian_delete_note`'s `ctx.requestInput` confirmation needs a stateful session on a 2025-era HTTP client. Under `stateless` the round trip is refused before any vault call, now as a typed `InvalidRequest` carrying `data.reason: "client_capability_missing"` where mcp-ts-core served a bare `isError` text block before 0.13.5. `teardown` closes `ObsidianService`'s undici dispatcher, releasing keep-alive sockets to the Local REST API and Omnisearch.
12
+ - **Every `errors[]` reason the service layer raises declares `thrownBy: 'service'`** (path policy, periodic-note routing, ambiguous case matches), and `obsidian_delete_note`'s `cancelled` reason carries `severity: 'notice'` — both are mcp-ts-core 0.13.5/0.13.6 error-contract metadata; the entries stay advertised and thrown exactly as before.
13
+
14
+ ## Changed
15
+
16
+ - **Tool error text now closes with the terms a caller branches on** — `(reason <reason>)`, joined by ` · ` with `retryable` or `not retryable` where the contract declares it — and drops the `Recovery:` line when the message already states the hint verbatim (mcp-ts-core 0.13.5).
17
+ - **A schema-rejected tool call carries `data.reason: "invalid_arguments"`** and a synthesized `Recovery:` hint naming the offending field (mcp-ts-core 0.13.3).
18
+ - **Tool arguments pass an ordered pre-validation step** before schema parsing — client-added root keys are dropped, an undeclared key is rewritten to a case-folded match, and a JSON-stringified array is repaired and re-parsed once on failure (mcp-ts-core 0.13.4).
19
+
20
+ ## Fixed
21
+
22
+ - **`OBSIDIAN_BASE_URL` strips a trailing slash** ([#121](https://github.com/cyanheads/obsidian-mcp-server/issues/121)) — a slash-terminated value doubled the separator on every request path and 404'd the whole vault.
23
+ - **A failed request's log cause names the URL that was actually fetched** ([#122](https://github.com/cyanheads/obsidian-mcp-server/issues/122)), so a malformed base URL no longer reads as a clean 404 against a healthy vault. The client-facing `path` and message are unchanged.
24
+ - **`obsidian_manage_frontmatter` (`delete`) and `obsidian_manage_tags` refuse to write instead of destroying content** when the note's frontmatter doesn't parse as a mapping or the mutation leaves YAML the emitter can't re-emit — a dangling alias, for instance ([#123](https://github.com/cyanheads/obsidian-mcp-server/issues/123), [#124](https://github.com/cyanheads/obsidian-mcp-server/issues/124)). Both tools declare the new `frontmatter_invalid` reason; nothing is written on refusal, and `obsidian_manage_tags` skips the inline half too under `location: "both"`. `obsidian_replace_in_note` already declared that reason and picks up the re-emit half of the check, so a replacement that strands an alias is refused rather than written.
25
+ - **Tag edits mutate an existing `tags:` sequence node in place** ([#123](https://github.com/cyanheads/obsidian-mcp-server/issues/123)) rather than replacing it with a normalized string set, so non-string entries, comments, and untouched item formatting survive.
26
+ - **A rewritten frontmatter block keeps the note's own line ending** (LF or CRLF) instead of always emitting LF, and deleting the last key drops only the blank separator lines rather than the body's leading indentation ([#125](https://github.com/cyanheads/obsidian-mcp-server/issues/125)).
27
+ - **`obsidian://vault/{+path}` percent-decodes the captured path** ([#132](https://github.com/cyanheads/obsidian-mcp-server/issues/132)) — a note name with a space, non-ASCII character, or literal `%` never resolved before; a malformed escape decodes leniently instead of throwing.
28
+
29
+ ## Security
30
+
31
+ - **TLS certificate verification is scoped to each request to an `https:` `OBSIDIAN_BASE_URL`** ([#131](https://github.com/cyanheads/obsidian-mcp-server/issues/131)) instead of `OBSIDIAN_VERIFY_SSL=false` setting `NODE_TLS_REJECT_UNAUTHORIZED=0` process-wide on Bun, which disabled certificate verification for every other HTTPS call the process made (a JWKS fetch, for instance). Node's dispatcher option and Bun's per-request `tls` option are both sent on every request, covering both runtimes with no runtime sniffing.
32
+
33
+ ## Dependencies
34
+
35
+ - `@cyanheads/mcp-ts-core` ^0.13.0 → ^0.13.6
36
+ - `zod` ^4.6.1 → ^4.6.5
37
+ - `js-yaml` ^5.4.1 → ^5.4.2
38
+ - `yaml` ^2.9.0 → ^2.9.1
39
+ - `@biomejs/biome` ^2.5.13 → ^2.5.14
40
+ - `@types/node` ^26.5.1 → ^26.6.1
41
+ - `tsc-alias` ^1.9.4 → ^1.9.5
42
+ - `vitest` ^5.0.0 → ^5.0.1
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "TLS certificate rejection and an unreachable Local REST API now surface as typed, actionable errors instead of a hintless InternalError, multi-word text search and inline tag matching are fixed, and a section write to a repeated heading path is rejected instead of landing on the wrong occurrence."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 3.5.5 — 2026-09-22
8
+
9
+ ## Fixed
10
+
11
+ - **A rejected TLS certificate and an unreachable Local REST API now surface as typed errors** — `certificate_rejected` names the runtime's TLS code and points at `OBSIDIAN_VERIFY_SSL` / `OBSIDIAN_BASE_URL`; `obsidian_unreachable` covers a closed port, wrong host, or disabled plugin and keeps its GET/PUT/DELETE retries. Both used to surface as a hintless `InternalError` ([#133](https://github.com/cyanheads/obsidian-mcp-server/issues/133), [#136](https://github.com/cyanheads/obsidian-mcp-server/issues/136)).
12
+ - **Error `data` no longer carries a route string as `path`** — a command-route error now carries `commandId`, and a route with no caller input (`/`, `/tags/`, `/search/`, …) carries neither ([#130](https://github.com/cyanheads/obsidian-mcp-server/issues/130)).
13
+ - **A multi-word `obsidian_search_notes` text query merges its per-token spans into one match location** instead of returning a near-identical context window per token ([#117](https://github.com/cyanheads/obsidian-mcp-server/issues/117)); offsets are also corrected when the context window's edge splits a surrogate pair, and a span's subject (note body or filename) is read from the plugin's `match.source` when it sends one ([#135](https://github.com/cyanheads/obsidian-mcp-server/issues/135)).
14
+ - **`#tag` detection now follows Obsidian's own grammar** — leading-digit and non-ASCII tags are read and written, HTML comments and math spans are skipped, and the left-boundary and all-digit rules match Obsidian's readback ([#127](https://github.com/cyanheads/obsidian-mcp-server/issues/127), [#138](https://github.com/cyanheads/obsidian-mcp-server/issues/138)).
15
+ - **Inline tag scanning stays linear in note length** — the inline-math check finds each span's closer from a single pass over the note, so a paragraph of `$` signs that never close, such as a table of prices, is not rescanned from each one ([#143](https://github.com/cyanheads/obsidian-mcp-server/issues/143)).
16
+ - **A document-map heading locator now round-trips through `obsidian_get_note` section reads at any depth** ([#128](https://github.com/cyanheads/obsidian-mcp-server/issues/128)), and a section write against a full path that repeats in the note is rejected with `ambiguous_section` instead of silently landing on the wrong occurrence ([#137](https://github.com/cyanheads/obsidian-mcp-server/issues/137)); `SectionSchema.target` and the write-tool descriptions document both cases ([#129](https://github.com/cyanheads/obsidian-mcp-server/issues/129)).
17
+
18
+ ## Dependencies
19
+
20
+ - `@types/node` ^26.6.1 → ^26.6.2
@@ -6,8 +6,8 @@
6
6
 
7
7
  # Required. One-line GitHub Release-style headline. 350 character cap — a
8
8
  # ceiling, not a target. Default short and scannable. Don't pad, don't stitch
9
- # unrelated changes with commas/semicolons into an inventory — pick the
10
- # headline, like a tag's theme line. Quotes required: unquoted YAML treats
9
+ # unrelated changes with commas/semicolons into an inventory — pick the one
10
+ # headline the release is about. Quotes required: unquoted YAML treats
11
11
  # `: ` inside the value as a key separator and fails GitHub's strict parser.
12
12
  summary: ""
13
13
 
@@ -119,11 +119,11 @@ security: false
119
119
  TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
120
120
  via `gh release create --notes-from-tag`. It is a condensed digest of this
121
121
  entry, never a copy, and its format is owned by the `release-and-publish`
122
- skill (step 4, "Create the annotated tag"): the entry's `summary:` as the
123
- theme line without the version, flat headline bullets — no Keep-a-Changelog
124
- section headers, no gates line — at most one deps line, issue backlinks,
125
- and the changelog link last. In release-PR mode the `git-wrapup` skill
126
- authors that digest as the PR body's `## Changes` and the tag copies it.
122
+ skill (step 4, "Create the annotated tag"): a short subject line without the
123
+ version, flat headline bullets — no Keep-a-Changelog section headers, no
124
+ gates line — at most one deps line, issue backlinks, and the changelog link
125
+ last. In release-PR mode the `git-wrapup` skill authors those bullets as the
126
+ PR body's `## Changes` and the tag copies them.
127
127
  -->
128
128
 
129
129
  ## Added
@@ -6,7 +6,7 @@
6
6
  import { z } from '@cyanheads/mcp-ts-core';
7
7
  declare const ServerConfigSchema: z.ZodObject<{
8
8
  apiKey: z.ZodString;
9
- baseUrl: z.ZodDefault<z.ZodString>;
9
+ baseUrl: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>;
10
10
  verifySsl: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCodec<z.ZodString, z.ZodBoolean>]>>;
11
11
  requestTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
12
12
  enableCommands: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCodec<z.ZodString, z.ZodBoolean>]>>;
@@ -1 +1 @@
1
- {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAgF3C,QAAA,MAAM,kBAAkB;;;;;;;;;;iBAgDtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAa9C;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
1
+ {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAgF3C,QAAA,MAAM,kBAAkB;;;;;;;;;;iBAuDtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAa9C;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -91,10 +91,17 @@ const ServerConfigSchema = z.object({
91
91
  .string()
92
92
  .url()
93
93
  .default('http://127.0.0.1:27123')
94
- .describe('Base URL of the Obsidian Local REST API. Defaults to http://127.0.0.1:27123 — enable "Non-encrypted (HTTP) Server" in the plugin settings to match. Use https://127.0.0.1:27124 to hit the always-on HTTPS port (self-signed cert; pair with OBSIDIAN_VERIFY_SSL=false).'),
94
+ .describe('Base URL of the Obsidian Local REST API. Defaults to http://127.0.0.1:27123 — enable "Non-encrypted (HTTP) Server" in the plugin settings to match. Use https://127.0.0.1:27124 to hit the always-on HTTPS port (self-signed cert; pair with OBSIDIAN_VERIFY_SSL=false). A trailing slash is stripped — writing one is natural and every request path already starts with `/`, so leaving it would double the separator and 404 every endpoint.')
95
+ /**
96
+ * Normalized here rather than at each `${baseUrl}${path}` join: the schema
97
+ * is the one place that runs before anything reads the value, so a single
98
+ * transform covers `#request`, the HEAD size probe, the capability probe,
99
+ * and `deriveOmnisearchUrl` alike (issue #121).
100
+ */
101
+ .transform((url) => url.replace(/\/+$/, '')),
95
102
  verifySsl: envBoolean
96
103
  .default(false)
97
- .describe("Whether to verify the TLS certificate on the Obsidian endpoint. Defaults to false because the plugin uses a self-signed cert. On Node, the dispatcher's `rejectUnauthorized` option handles this without any process-wide change. On Bun, the runtime ignores that option, so the service additionally sets `NODE_TLS_REJECT_UNAUTHORIZED=0` — that fallback is scoped to Bun only."),
104
+ .describe("Whether to verify the TLS certificate on the Obsidian endpoint. Defaults to false because the plugin uses a self-signed cert. The relaxation is scoped to this server's own requests to an `https:` OBSIDIAN_BASE_URL — nothing else the process talks to is affected, and a plain-HTTP base URL is unaffected either way."),
98
105
  requestTimeoutMs: z.coerce
99
106
  .number()
100
107
  .int()
@@ -1 +1 @@
1
- {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"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"}