obsidian-mcp-server 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +40 -27
- package/CLAUDE.md +40 -27
- package/Dockerfile +90 -47
- package/README.md +130 -151
- package/changelog/3.6.x/3.6.1.md +29 -0
- package/changelog/3.7.x/3.7.0.md +34 -0
- package/dist/config/server-config.d.ts +1 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +4 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +30 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts +7 -5
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js +12 -8
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +32 -10
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +30 -8
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +230 -294
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/index.js +9 -3
- package/dist/mcp-server/tools/definitions/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +108 -10
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +254 -133
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +62 -21
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js +1 -2
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +6 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +9 -12
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +7 -5
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +14 -9
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +1 -2
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +2 -2
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +4 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +15 -4
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +2 -3
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +2 -5
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
- package/dist/services/obsidian/obsidian-service.d.ts +7 -0
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
- package/dist/services/obsidian/obsidian-service.js +72 -35
- package/dist/services/obsidian/obsidian-service.js.map +1 -1
- package/dist/services/obsidian/patch-instruction.d.ts +20 -1
- package/dist/services/obsidian/patch-instruction.d.ts.map +1 -1
- package/dist/services/obsidian/patch-instruction.js +30 -0
- package/dist/services/obsidian/patch-instruction.js.map +1 -1
- package/dist/services/obsidian/path-policy.d.ts +23 -1
- package/dist/services/obsidian/path-policy.d.ts.map +1 -1
- package/dist/services/obsidian/path-policy.js +45 -3
- package/dist/services/obsidian/path-policy.js.map +1 -1
- package/dist/services/obsidian/section-extractor.d.ts +24 -0
- package/dist/services/obsidian/section-extractor.d.ts.map +1 -1
- package/dist/services/obsidian/section-extractor.js +37 -0
- package/dist/services/obsidian/section-extractor.js.map +1 -1
- package/manifest.json +9 -1
- package/package.json +13 -10
- package/server.json +24 -24
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/obsidian-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -23,204 +23,168 @@
|
|
|
23
23
|
|
|
24
24
|
## Overview
|
|
25
25
|
|
|
26
|
-
Read,
|
|
26
|
+
Obsidian vault notes over the Local REST API plugin. Read, search, and write notes, edit single headings, blocks, and frontmatter fields in place, and manage tags, with folder-scoped read/write permissions built in. Runs as a stdio process or a local Streamable HTTP server.
|
|
27
27
|
|
|
28
28
|
### Tools
|
|
29
29
|
|
|
30
30
|
| Tool | Description |
|
|
31
31
|
|:---|:---|
|
|
32
|
-
| `obsidian_get_note` | Read a note as raw content, full structured form
|
|
33
|
-
| `obsidian_list_notes` | List notes and
|
|
34
|
-
| `obsidian_list_tags` | List vault tags with usage counts,
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `obsidian_execute_command` |
|
|
32
|
+
| `obsidian_get_note` | Read a note as raw content, full structured form, document map, or a single section |
|
|
33
|
+
| `obsidian_list_notes` | List notes and folders under a vault path, recursively, with extension and name filters |
|
|
34
|
+
| `obsidian_list_tags` | List vault tags with usage counts, most-used first |
|
|
35
|
+
| `obsidian_search_notes` | Search by text, JSONLogic, or BM25-ranked Omnisearch when that plugin is reachable |
|
|
36
|
+
| `obsidian_write_note` | Create a note, replace one section, or overwrite a whole file with `overwrite: true` |
|
|
37
|
+
| `obsidian_append_to_note` | Append to a note (creating it if missing) or to one heading, block, or frontmatter field |
|
|
38
|
+
| `obsidian_patch_note` | Append, prepend, or replace against one heading, block reference, or frontmatter field |
|
|
39
|
+
| `obsidian_replace_in_note` | Literal or regex search-replace inside one note, body-only by default |
|
|
40
|
+
| `obsidian_manage_frontmatter` | Get, set, or delete one frontmatter key |
|
|
41
|
+
| `obsidian_manage_tags` | Add, remove, or list a note's tags in frontmatter, inline, or both |
|
|
42
|
+
| `obsidian_delete_note` | Permanently delete a note, optionally after the user confirms (`OBSIDIAN_DELETE_ELICITATION`) |
|
|
43
|
+
| `obsidian_open_in_ui` | Open a file in the Obsidian app, optionally in a new pane |
|
|
44
|
+
| `obsidian_list_commands` | List command-palette commands (opt-in via `OBSIDIAN_ENABLE_COMMANDS`) |
|
|
45
|
+
| `obsidian_execute_command` | Run a command-palette command by ID (opt-in via `OBSIDIAN_ENABLE_COMMANDS`) |
|
|
46
46
|
|
|
47
47
|
### Resources
|
|
48
48
|
|
|
49
49
|
| Resource | Description |
|
|
50
50
|
|:---|:---|
|
|
51
|
-
| `obsidian://vault/{+path}` | A note
|
|
52
|
-
| `obsidian://tags` |
|
|
53
|
-
| `obsidian://status` |
|
|
51
|
+
| `obsidian://vault/{+path}` | A note's content, frontmatter, tags, and file metadata |
|
|
52
|
+
| `obsidian://tags` | Every vault tag with its usage count, as an uncapped snapshot |
|
|
53
|
+
| `obsidian://status` | Plugin reachability, auth status, versions, and registered API extensions |
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Note and tag data are also reachable through tools (`obsidian_get_note`, `obsidian_list_tags`); `obsidian://status` has no tool equivalent.
|
|
56
56
|
|
|
57
57
|
## Capability reference
|
|
58
58
|
|
|
59
59
|
### `obsidian_get_note` <sub>tool</sub>
|
|
60
60
|
|
|
61
|
-
- `format
|
|
62
|
-
-
|
|
63
|
-
-
|
|
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`
|
|
61
|
+
- `target` is a vault `path`, the `active` file, or a `periodic` note (`daily` through `yearly`, optional `date`); `format` is `content`, `full`, `document-map`, or `section`, and `full` takes `includeLinks: true` for vault-internal outgoing links
|
|
62
|
+
- `result.format` discriminates the payload; a `section` read that matches several headings returns the first and lists every full path in `candidates`
|
|
63
|
+
- A `path` with no exact match but one case-insensitive match in its folder reads that file: `result.path` is the real name, `requestedPath` the one sent, and a `notice` names both
|
|
66
64
|
|
|
67
65
|
---
|
|
68
66
|
|
|
69
67
|
### `obsidian_list_notes` <sub>tool</sub>
|
|
70
68
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
- Per-directory `truncated: true` marks entries cut off by the depth limit or by path policy
|
|
69
|
+
- Walks from `path` (default vault root) to `depth` 1–20 (default 2), filtered by `extension` and `nameRegex` (≤256 chars); a folder that fails `nameRegex` is not walked
|
|
70
|
+
- Returns `entries[]` (`file` / `directory`), `totals`, and `appliedFilters`; the walk stops at 1,000 entries with `excluded.reason: "entry_cap"`, and a folder the depth limit stopped carries `truncated: true`
|
|
71
|
+
- With `OBSIDIAN_READ_PATHS` set, a listing holds only readable entries and the folders leading to the scope; those folders are walked and accepted as `path`
|
|
75
72
|
|
|
76
73
|
---
|
|
77
74
|
|
|
78
75
|
### `obsidian_list_tags` <sub>tool</sub>
|
|
79
76
|
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
- Not narrowed by `OBSIDIAN_READ_PATHS` — tag names (never note contents) can surface from outside the read scope
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
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`
|
|
77
|
+
- `nameRegex` (≤256 chars) and `minCount` narrow the set, then tags are ranked by count and capped at `limit` (default 200, max 10000); hierarchical parents count (`work/tasks` adds to `work`)
|
|
78
|
+
- When the cap withholds tags, the response carries `truncated`, `shown`, and `cap`
|
|
79
|
+
- With `OBSIDIAN_READ_PATHS` set, only tags from readable notes are listed, and `count` is the number of readable notes carrying the tag or a tag nested under it
|
|
92
80
|
|
|
93
81
|
---
|
|
94
82
|
|
|
95
83
|
### `obsidian_search_notes` <sub>tool</sub>
|
|
96
84
|
|
|
97
|
-
- `mode: "text"
|
|
98
|
-
- `
|
|
99
|
-
-
|
|
100
|
-
- No dedicated backlinks tool — express "what links here" via `jsonlogic`: `{"regexp": ["\\[\\[Target Note(\\||#|\\]\\])", {"var": "content"}]}`
|
|
85
|
+
- `mode: "text"` requires every whitespace-split token of `query` as a case-insensitive substring of the filename or body (quotes are literal), shaped by `contextLength` (default 100), `pathPrefix`, and `maxMatchesPerHit` (default 10); `mode: "jsonlogic"` evaluates a `logic` tree over `path`, `content`, `frontmatter.<key>`, `tags`, and `stat`, with `glob` / `regexp` taking `[PATTERN, VALUE]`
|
|
86
|
+
- `result.mode` discriminates the payload; every mode reports `totalCount` and pages via `nextCursor`, and a text hit clipped to `maxMatchesPerHit` carries `truncated` and `totalMatches`
|
|
87
|
+
- `mode: "omnisearch"` (BM25 ranking, quoted phrases, `-exclusion`, `path:` / `ext:` filters) is offered only when the Omnisearch plugin answered at startup; its 50-hit upstream cap sets `truncated: true`
|
|
101
88
|
|
|
102
89
|
---
|
|
103
90
|
|
|
104
91
|
### `obsidian_write_note` <sub>tool</sub>
|
|
105
92
|
|
|
106
|
-
-
|
|
107
|
-
- With `section
|
|
108
|
-
- Output reports `created`, plus `previousSizeInBytes` / `currentSizeInBytes` on every call to spot an accidental clobber or a mistyped path
|
|
93
|
+
- `target` and `content`, with optional `section` and `contentType` (`markdown` / `json`); a whole-file write to an existing note fails with `file_exists` unless `overwrite: true`
|
|
94
|
+
- With `section`, replaces only that heading, block, or frontmatter field and keeps the heading line; output reports `created`, `sectionTargeted`, and the resolved `sectionTarget`
|
|
109
95
|
|
|
110
96
|
---
|
|
111
97
|
|
|
112
98
|
### `obsidian_append_to_note` <sub>tool</sub>
|
|
113
99
|
|
|
114
|
-
- Without `section
|
|
115
|
-
-
|
|
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
|
|
100
|
+
- Without `section`, appends to the file or creates it (`created: true`); with `section`, appends to that heading, block, or frontmatter field of an existing note, and `createTargetIfMissing: true` creates the section
|
|
101
|
+
- A section append whose content is already at the target fails with `content_preexists`; block targets add no separator, so start `content` with a newline if you want one
|
|
118
102
|
|
|
119
103
|
---
|
|
120
104
|
|
|
121
105
|
### `obsidian_patch_note` <sub>tool</sub>
|
|
122
106
|
|
|
123
|
-
- `operation: "append" | "prepend" | "replace"` against one
|
|
124
|
-
-
|
|
125
|
-
- `patchOptions`: `createTargetIfMissing`, `applyIfContentPreexists` (idempotency guard — otherwise `content_preexists`), `trimTargetWhitespace` (plugin v4.x only; v5.0 and later place the blank lines around inserted content themselves)
|
|
107
|
+
- `operation: "append" | "prepend" | "replace"` against one `section` of an existing note; `patchOptions` takes `createTargetIfMissing`, `applyIfContentPreexists`, and `trimTargetWhitespace` (plugin v4.x only)
|
|
108
|
+
- Echoes the resolved `section` and `operation`; a repeat of content already at the target fails with `content_preexists` unless `applyIfContentPreexists: true`
|
|
126
109
|
|
|
127
110
|
---
|
|
128
111
|
|
|
129
112
|
### `obsidian_replace_in_note` <sub>tool</sub>
|
|
130
113
|
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
- `perReplacement[]` reports `bodyCount` / `frontmatterCount` per entry; `totalReplacements` sums them
|
|
114
|
+
- `replacements[]` run in order, each over the previous one's output; each takes `useRegex` (≤1024 chars), `caseSensitive` (default `true`), `wholeWord`, `flexibleWhitespace` (literal mode only), and `replaceAll` (default `true`)
|
|
115
|
+
- Returns `totalReplacements` and `perReplacement[]` with `bodyCount` / `frontmatterCount`
|
|
116
|
+
- `scope: "body"` (default) leaves frontmatter byte-identical; `"frontmatter"` and `"both"` re-parse the YAML afterward and write nothing if it breaks (`frontmatter_invalid`)
|
|
135
117
|
|
|
136
118
|
---
|
|
137
119
|
|
|
138
120
|
### `obsidian_manage_frontmatter` <sub>tool</sub>
|
|
139
121
|
|
|
140
|
-
- `operation: "get" | "set" | "delete"` on
|
|
141
|
-
- `get`
|
|
142
|
-
- `set` / `delete` return the full `frontmatter` after the change plus `previousSizeInBytes` / `currentSizeInBytes`
|
|
122
|
+
- `operation: "get" | "set" | "delete"` on one `key`; `set` requires a JSON-typed `value`
|
|
123
|
+
- `get` returns `exists` and `value` (`null` when absent); `set` and `delete` return the full `frontmatter` after the change, and a `delete` against unparseable YAML fails with `frontmatter_invalid` without writing
|
|
143
124
|
|
|
144
125
|
---
|
|
145
126
|
|
|
146
127
|
### `obsidian_manage_tags` <sub>tool</sub>
|
|
147
128
|
|
|
148
|
-
- `operation: "add" | "remove" | "list"`; `location: "frontmatter"` (default,
|
|
149
|
-
-
|
|
150
|
-
- Inline
|
|
151
|
-
- `add` / `remove` report `applied` vs. `skipped` tags plus the full `tags` set after the change; `list` ignores the input `tags` array
|
|
129
|
+
- `operation: "add" | "remove" | "list"` with `tags`; `location: "frontmatter"` (default, the `tags:` array), `"inline"` (body `#tag`; `add` appends at end of file), or `"both"`
|
|
130
|
+
- `add` / `remove` report `applied`, `skipped`, and the resulting `tags`; `list` returns `frontmatter`, `inline`, and `all`
|
|
131
|
+
- Inline detection follows Obsidian's tag grammar: code, wikilinks, images, link destinations, HTML, and math are skipped, and `%% … %%` comments are read
|
|
152
132
|
|
|
153
133
|
---
|
|
154
134
|
|
|
155
135
|
### `obsidian_delete_note` <sub>tool</sub>
|
|
156
136
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
137
|
+
- Takes a `target` (path, active file, or periodic note) and checks it against the write scope first; a folder path fails with `path_is_directory`. There is no API-level undo, only Obsidian's local trash
|
|
138
|
+
- The path must match exactly, letter case included, even on a case-insensitive filesystem. A different-case or extension-less spelling fails `note_missing` before anything is deleted, naming the near matches in `suggestions`
|
|
139
|
+
- By default the note is deleted on the first call, with no confirmation; `OBSIDIAN_WRITE_PATHS` and `OBSIDIAN_READ_ONLY` bound what it can reach
|
|
140
|
+
- With `OBSIDIAN_DELETE_ELICITATION=true`, the first call answers with a confirmation request naming the path and byte size, and the note is deleted only after the user accepts. Declining fails with `cancelled`, and a client without elicitation support cannot delete
|
|
141
|
+
- In that mode an answer counts only against the single-use consent record stored when the prompt was shown, bound to the caller, the path, and the note's content then. A pre-supplied or replayed answer, or one given after the note changed, gets a fresh prompt instead
|
|
142
|
+
- Consent records live in the server's storage provider (`STORAGE_PROVIDER_TYPE`, default `in-memory`, process-local). That works for stdio or a single HTTP instance; several instances behind one endpoint need a shared provider (`filesystem`, `supabase`, or `cloudflare-d1`, never `cloudflare-kv`)
|
|
160
143
|
|
|
161
144
|
---
|
|
162
145
|
|
|
163
146
|
### `obsidian_open_in_ui` <sub>tool</sub>
|
|
164
147
|
|
|
165
|
-
- `failIfMissing` (default `true`)
|
|
166
|
-
- `
|
|
167
|
-
-
|
|
168
|
-
|
|
148
|
+
- `path`, `failIfMissing` (default `true`), and `newLeaf` (open in a split pane); with `failIfMissing: false` a missing file is created, which needs write access
|
|
149
|
+
- `createdIfMissing` reports which branch ran
|
|
150
|
+
- A case-mismatched `path` with one case-insensitive match opens that file instead of creating another, reporting the path sent as `requestedPath` with a `notice`
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### `obsidian_list_commands` <sub>tool</sub>
|
|
155
|
+
|
|
156
|
+
- Optional `nameRegex` (≤256 chars) matched against each command's display name
|
|
157
|
+
- Returns `commands[]` of `id` and `name`, where `id` feeds `obsidian_execute_command`
|
|
158
|
+
- Listed only when `OBSIDIAN_ENABLE_COMMANDS=true` and `OBSIDIAN_READ_ONLY` is off
|
|
169
159
|
|
|
170
160
|
---
|
|
171
161
|
|
|
172
162
|
### `obsidian_execute_command` <sub>tool</sub>
|
|
173
163
|
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
- Behavior is command-dependent — some are destructive (delete file, close vault), some open UI
|
|
164
|
+
- `commandId` from `obsidian_list_commands`; returns `executed: true`, or fails with `command_unknown` for an unregistered ID
|
|
165
|
+
- Runs with the authority of a keyboard shortcut, so some commands are destructive (delete file, close vault); gated like `obsidian_list_commands`
|
|
177
166
|
|
|
178
167
|
---
|
|
179
168
|
|
|
180
169
|
### `obsidian://vault/{+path}` <sub>resource</sub>
|
|
181
170
|
|
|
182
|
-
-
|
|
183
|
-
-
|
|
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
|
|
171
|
+
- `{+path}` captures everything after `/vault/`, slashes included; literal and percent-encoded paths resolve to the same note
|
|
172
|
+
- Returns `path`, `content`, `frontmatter`, `tags`, and `stat`, the same shape as `obsidian_get_note` with `format: "full"`; failures are `path_forbidden`, `note_missing`, or `path_is_directory`
|
|
186
173
|
|
|
187
174
|
---
|
|
188
175
|
|
|
189
176
|
### `obsidian://tags` <sub>resource</sub>
|
|
190
177
|
|
|
191
|
-
-
|
|
192
|
-
-
|
|
178
|
+
- Every tag with its `count`, uncapped and unsorted, hierarchical parents included
|
|
179
|
+
- With `OBSIDIAN_READ_PATHS` set, only tags from readable notes, counted the same way as `obsidian_list_tags`
|
|
180
|
+
- No ranking or filters; `obsidian_list_tags` gives the count-ranked, capped view
|
|
193
181
|
|
|
194
182
|
---
|
|
195
183
|
|
|
196
184
|
### `obsidian://status` <sub>resource</sub>
|
|
197
185
|
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
- Still reports reachability when the API key is misconfigured; only `authenticated` reflects the key's validity
|
|
201
|
-
|
|
202
|
-
## Path policy (folder-scoped permissions)
|
|
203
|
-
|
|
204
|
-
Three optional env vars gate which vault paths each tool can target. **Default unset = full vault** for both reads and writes — backwards compatible.
|
|
205
|
-
|
|
206
|
-
| Goal | Config |
|
|
207
|
-
|:---|:---|
|
|
208
|
-
| Default (current behavior) | all unset |
|
|
209
|
-
| Read everywhere, write only in `projects/` and `scratch/` | `OBSIDIAN_WRITE_PATHS=projects/,scratch/` |
|
|
210
|
-
| Read only `public/`, write only `public/inbox/` | `OBSIDIAN_READ_PATHS=public/`, `OBSIDIAN_WRITE_PATHS=public/inbox/` |
|
|
211
|
-
| Read-only deployment — no writes anywhere | `OBSIDIAN_READ_ONLY=true` |
|
|
212
|
-
|
|
213
|
-
**Matching is prefix-based with implicit recursion**, case-insensitive, with trailing slashes normalized. `projects/` matches `projects/a.md`, `projects/sub/b.md`, etc.
|
|
214
|
-
|
|
215
|
-
**Write paths are implicitly readable** — you can't sanely edit what you can't see. So a read passes when the target matches `READ_PATHS` *or* `WRITE_PATHS`.
|
|
216
|
-
|
|
217
|
-
**`OBSIDIAN_READ_ONLY=true` short-circuits before the path checks** — every write tool and the command-palette pair are wrapped with `disabledTool()` at startup (absent from `tools/list`), and any write that still reaches the service is denied at runtime regardless of `WRITE_PATHS`.
|
|
218
|
-
|
|
219
|
-
Denies are typed `path_forbidden` (JSON-RPC code `Forbidden`) with the active scope echoed back in `data.recovery.hint` and `data.activeScope`, so the LLM can self-correct without inspecting server logs. Search results from `obsidian_search_notes` are filtered against `READ_PATHS` silently — surfacing a "we hid N hits" indicator would defeat the gate.
|
|
220
|
-
|
|
221
|
-
**Tag listing is vault-wide.** `obsidian_list_tags` and the `obsidian://tags` resource aggregate tag names across the whole vault and are *not* narrowed by `OBSIDIAN_READ_PATHS` — they take no path to gate, so tag names (never note contents) from outside the read scope can surface.
|
|
222
|
-
|
|
223
|
-
The startup banner logs the active scope so operators can verify their config at boot.
|
|
186
|
+
- `status`, `service`, `authenticated`, `versions`, `manifest`, and `apiExtensions[]`; still answers with a wrong API key, reporting `authenticated: false`
|
|
187
|
+
- On plugin v5.0.2 and later, check `apiExtensions` for `local-rest-api-periodic-notes` before using `periodic` targets
|
|
224
188
|
|
|
225
189
|
## Features
|
|
226
190
|
|
|
@@ -228,22 +192,23 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
|
|
|
228
192
|
|
|
229
193
|
Obsidian-specific:
|
|
230
194
|
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
234
|
-
-
|
|
235
|
-
-
|
|
195
|
+
- Typed client for the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin; section writes speak markdown-patch 2.0 to plugin v5.0 and later and 1.x to v4.x, chosen from the reported plugin version
|
|
196
|
+
- Heading targets take a full `Parent::Child` path or a bare leaf name; writes reject an ambiguous one with `ambiguous_section` and its `candidates`, unless exactly one match is a top-level heading. On plugin v5.0 and later, content added to a heading is set off by a blank line (a list item continues an adjacent list), and a heading inside it must sit below the section's level (`heading_outside_section`)
|
|
197
|
+
- Folder-scoped read/write permissions, a read-only switch, and an opt-in command-palette pair (see [Path policy](#path-policy)); server-level `instructions` on `initialize` report the active policy
|
|
198
|
+
- `obsidian_get_note` and `obsidian_open_in_ui` retry a case-mismatched path against the real filename, disclosed as `requestedPath` plus a `notice`, and add `Did you mean` suggestions to a miss; writes and deletes match the exact path
|
|
199
|
+
- Regex inputs are capped (`nameRegex` at 256 chars, `useRegex` at 1024) and rejected with `regex_unsafe` when they nest quantifiers
|
|
200
|
+
- Backlinks have no dedicated tool; `obsidian_search_notes` in `jsonlogic` mode finds them with `{"regexp": ["\\[\\[Target Note(\\\\?\\||#|\\]\\])", {"var": "content"}]}`, which also matches the table-safe `[[Target Note\|Alias]]`
|
|
236
201
|
|
|
237
202
|
Agent-friendly output:
|
|
238
203
|
|
|
239
|
-
- Recovery-guided errors
|
|
240
|
-
- Size
|
|
241
|
-
- Ambiguity surfaced
|
|
242
|
-
- Discriminated output contracts
|
|
204
|
+
- Recovery-guided errors: every declared failure carries a `reason`, a JSON-RPC code, and a `recovery.hint` written for that case
|
|
205
|
+
- Size deltas: every mutating tool returns `previousSizeInBytes` / `currentSizeInBytes`, so a caller can spot an accidental clobber without a follow-up read
|
|
206
|
+
- Ambiguity surfaced as data: shared heading names return `candidates`, and tag operations report `applied` vs. `skipped`
|
|
207
|
+
- Discriminated output contracts: `format` on `obsidian_get_note`, `mode` on `obsidian_search_notes`, `operation` on `obsidian_manage_frontmatter` and `obsidian_manage_tags`
|
|
243
208
|
|
|
244
209
|
## Getting started
|
|
245
210
|
|
|
246
|
-
Add the following to your MCP client configuration file. The Obsidian Local REST API plugin must be installed and enabled in your vault
|
|
211
|
+
Add the following to your MCP client configuration file. The Obsidian Local REST API plugin must be installed and enabled in your vault; see [Prerequisites](#prerequisites).
|
|
247
212
|
|
|
248
213
|
```json
|
|
249
214
|
{
|
|
@@ -301,9 +266,9 @@ Or with Docker:
|
|
|
301
266
|
}
|
|
302
267
|
```
|
|
303
268
|
|
|
304
|
-
|
|
269
|
+
Inside a container, the default `OBSIDIAN_BASE_URL` (`http://127.0.0.1:27123`) is the container's own loopback. Add `-e OBSIDIAN_BASE_URL=http://host.docker.internal:27123` (Docker Desktop) or run with `--network host` (Linux) to reach the plugin on your host.
|
|
305
270
|
|
|
306
|
-
For Streamable HTTP, set the transport and start the server
|
|
271
|
+
For Streamable HTTP, set the transport and start the server:
|
|
307
272
|
|
|
308
273
|
```sh
|
|
309
274
|
MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
|
|
@@ -313,10 +278,12 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
|
|
|
313
278
|
### Prerequisites
|
|
314
279
|
|
|
315
280
|
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
316
|
-
- The [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin,
|
|
317
|
-
-
|
|
318
|
-
-
|
|
319
|
-
-
|
|
281
|
+
- The [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin, v4.0.0 or later, enabled in your vault. Generate an API key under **Settings → Community Plugins → Local REST API** and set it as `OBSIDIAN_API_KEY`.
|
|
282
|
+
- The server defaults to `http://127.0.0.1:27123`, so enable **"Non-encrypted (HTTP) Server"** in the plugin settings, or set `OBSIDIAN_BASE_URL=https://127.0.0.1:27124` for the always-on HTTPS port (its self-signed cert is accepted while `OBSIDIAN_VERIFY_SSL=false`, the default).
|
|
283
|
+
- Periodic-note targets work natively on plugin v5.0.1 and earlier. From v5.0.2 they need the [periodic-notes API extension](https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes); without it they fail with `periodic_unsupported`.
|
|
284
|
+
- On plugin v4.x, heading section writes to a note with CRLF (Windows) line endings land mid-line: the plugin's markdown-patch 1.x engine misplaces them, and the server can't correct it. Plugin v5.0 and later handle CRLF notes.
|
|
285
|
+
- Plugin v6.0 drops markdown-patch 1.x, which two table-row writes (`contentType: "json"`) still use: rows under a heading, and rows through a block ID on its own line below the table. On v6.0, target the table by an ID on its last row.
|
|
286
|
+
- With `OBSIDIAN_DELETE_ELICITATION=true`, an MCP client that supports elicitation and renders its form, to use `obsidian_delete_note`. Every other tool, and the default delete, works without it.
|
|
320
287
|
|
|
321
288
|
### Installation
|
|
322
289
|
|
|
@@ -349,30 +316,42 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
|
|
|
349
316
|
|
|
350
317
|
| Variable | Description | Default |
|
|
351
318
|
|:---------|:------------|:--------|
|
|
352
|
-
| `OBSIDIAN_API_KEY` | **Required.** Bearer token for the
|
|
353
|
-
| `OBSIDIAN_BASE_URL` |
|
|
354
|
-
| `OBSIDIAN_VERIFY_SSL` | Verify the TLS certificate.
|
|
319
|
+
| `OBSIDIAN_API_KEY` | **Required.** Bearer token for the Local REST API plugin. | — |
|
|
320
|
+
| `OBSIDIAN_BASE_URL` | Local REST API base URL; `https://127.0.0.1:27124` is the always-on HTTPS port. A trailing slash is stripped. When nothing answers, calls fail with `obsidian_unreachable`. | `http://127.0.0.1:27123` |
|
|
321
|
+
| `OBSIDIAN_VERIFY_SSL` | Verify the plugin's TLS certificate. Off by default for its self-signed cert; the relaxation applies only to an `https:` `OBSIDIAN_BASE_URL`. With `true`, an untrusted cert fails calls with `certificate_rejected`. | `false` |
|
|
355
322
|
| `OBSIDIAN_REQUEST_TIMEOUT_MS` | Per-request timeout in milliseconds. | `30000` |
|
|
356
|
-
| `OBSIDIAN_ENABLE_COMMANDS` |
|
|
357
|
-
| `OBSIDIAN_READ_PATHS` | Comma-separated
|
|
358
|
-
| `OBSIDIAN_WRITE_PATHS` | Comma-separated
|
|
359
|
-
| `OBSIDIAN_READ_ONLY` |
|
|
360
|
-
| `
|
|
323
|
+
| `OBSIDIAN_ENABLE_COMMANDS` | Enable `obsidian_list_commands` and `obsidian_execute_command`. Commands are opaque and can be destructive. | `false` |
|
|
324
|
+
| `OBSIDIAN_READ_PATHS` | Comma-separated folder allowlist for reads. See [Path policy](#path-policy). | unset (full vault) |
|
|
325
|
+
| `OBSIDIAN_WRITE_PATHS` | Comma-separated folder allowlist for writes. See [Path policy](#path-policy). | unset (full vault) |
|
|
326
|
+
| `OBSIDIAN_READ_ONLY` | Deny every write and disable the command-palette pair. | `false` |
|
|
327
|
+
| `OBSIDIAN_DELETE_ELICITATION` | Ask the user to confirm each `obsidian_delete_note` call through an elicitation form before deleting. Needs a client that renders elicitation, and a stateful session over HTTP (not required when `OBSIDIAN_READ_ONLY=true` disables the tool). Off, the delete runs on the first call. | `false` |
|
|
328
|
+
| `OBSIDIAN_OMNISEARCH_URL` | [Omnisearch](https://github.com/scambier/obsidian-omnisearch) HTTP server URL. Unset derives from the `OBSIDIAN_BASE_URL` host on port `51361`. Probed once at startup; the `omnisearch` search mode appears only if it answers. | derived |
|
|
361
329
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
362
|
-
| `
|
|
363
|
-
| `
|
|
364
|
-
| `
|
|
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` |
|
|
366
|
-
| `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments (landing page, Server Card, RFC 9728 metadata). | unset |
|
|
367
|
-
| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
|
|
368
|
-
| `MCP_AUTH_SECRET_KEY` | **Required when `MCP_AUTH_MODE=jwt`.** ≥32-char shared secret used to verify incoming JWTs. | — |
|
|
369
|
-
| `MCP_AUTH_DISABLE_SCOPE_CHECKS` | When `true`, bypasses per-tool scope enforcement after the auth-context presence check. Token signature, audience, issuer, and expiry validation remain intact. Use only when a custom claim can't be injected and combine with `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` / `OBSIDIAN_READ_ONLY` for access control. A `WARNING` is logged at startup whenever the bypass is active. | `false` |
|
|
330
|
+
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
|
|
331
|
+
| `MCP_SESSION_MODE` | HTTP session mode: `stateful`, `stateless`, or `auto`. With `OBSIDIAN_DELETE_ELICITATION=true` and `OBSIDIAN_READ_ONLY` off, the server requires a stateful session, because the confirmation needs one on 2025-era clients, so `stateless` fails startup over HTTP. No effect on stdio. | `stateful` |
|
|
332
|
+
| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
|
|
370
333
|
| `MCP_LOG_LEVEL` | Log level (RFC 5424). | `info` |
|
|
371
334
|
| `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
|
|
372
|
-
| `OTEL_ENABLED` | Enable [OpenTelemetry
|
|
335
|
+
| `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
|
|
373
336
|
|
|
374
337
|
See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
375
338
|
|
|
339
|
+
### Path policy
|
|
340
|
+
|
|
341
|
+
Three optional env vars limit which vault paths the tools can touch. Unset, reads and writes cover the full vault.
|
|
342
|
+
|
|
343
|
+
| Goal | Config |
|
|
344
|
+
|:---|:---|
|
|
345
|
+
| Read everywhere, write only in `projects/` and `scratch/` | `OBSIDIAN_WRITE_PATHS=projects/,scratch/` |
|
|
346
|
+
| Read only `public/`, write only `public/inbox/` | `OBSIDIAN_READ_PATHS=public/`, `OBSIDIAN_WRITE_PATHS=public/inbox/` |
|
|
347
|
+
| No writes anywhere | `OBSIDIAN_READ_ONLY=true` |
|
|
348
|
+
|
|
349
|
+
- Matching is by prefix, recursive, and case-insensitive; trailing slashes are normalized. Write paths are also readable.
|
|
350
|
+
- `OBSIDIAN_READ_ONLY=true` removes every write tool and the command-palette pair from `tools/list`, including `obsidian_manage_frontmatter` and `obsidian_manage_tags` (so their `get` / `list` go too). `obsidian_open_in_ui` still opens existing files but won't create one.
|
|
351
|
+
- A denial fails with `path_forbidden`, echoing the active scope in `data.activeScope` and the recovery hint. Search hits outside the read scope are dropped silently, and `obsidian_list_notes` lists only readable entries plus the folders leading to the scope, so a nested scope can be browsed to from the root.
|
|
352
|
+
- Tag listings (`obsidian_list_tags`, `obsidian://tags`) under `OBSIDIAN_READ_PATHS` cover readable notes only, counting notes rather than occurrences. Write paths and read-only alone leave them vault-wide.
|
|
353
|
+
- The startup log prints the active scope.
|
|
354
|
+
|
|
376
355
|
## Running the server
|
|
377
356
|
|
|
378
357
|
### Local development
|
|
@@ -404,22 +383,22 @@ docker build -t obsidian-mcp-server .
|
|
|
404
383
|
docker run --rm -e OBSIDIAN_API_KEY=your-key -p 3010:3010 obsidian-mcp-server
|
|
405
384
|
```
|
|
406
385
|
|
|
407
|
-
The
|
|
386
|
+
The image defaults to HTTP transport on `0.0.0.0`, stateful sessions, and logs in `/var/log/obsidian-mcp-server`. Point `OBSIDIAN_BASE_URL` at `http://host.docker.internal:27123` (Docker Desktop) or use `--network host` (Linux) to reach the plugin. OpenTelemetry peer dependencies are installed by default; build with `--build-arg OTEL_ENABLED=false` to omit them.
|
|
408
387
|
|
|
409
|
-
|
|
388
|
+
If the port is reachable from other machines, set `MCP_AUTH_MODE=jwt` (with `MCP_AUTH_SECRET_KEY`) or `oauth`. With the default `none`, every caller acts on your vault with your `OBSIDIAN_API_KEY`.
|
|
410
389
|
|
|
411
390
|
## Project structure
|
|
412
391
|
|
|
413
392
|
| Directory | Purpose |
|
|
414
393
|
|:----------|:--------|
|
|
415
|
-
| `src/index.ts` | `createApp()` entry point
|
|
394
|
+
| `src/index.ts` | `createApp()` entry point: registers tools and resources, probes Omnisearch, applies the read-only and command gates. |
|
|
416
395
|
| `src/config` | Server-specific environment variable parsing (`OBSIDIAN_*`) with Zod. |
|
|
417
|
-
| `src/services/obsidian` | Local REST API client,
|
|
418
|
-
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`) and shared
|
|
396
|
+
| `src/services/obsidian` | Local REST API client, path policy, markdown-patch format handling, frontmatter/section/tag parsing, domain types. |
|
|
397
|
+
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`) and shared schemas and helpers. |
|
|
419
398
|
| `src/mcp-server/resources` | Resource definitions (`*.resource.ts`). |
|
|
420
|
-
| `src/mcp-server/prompts` | Prompt definitions (
|
|
421
|
-
| `tests/` | Vitest tests
|
|
422
|
-
| `docs/` |
|
|
399
|
+
| `src/mcp-server/prompts` | Prompt definitions (none registered). |
|
|
400
|
+
| `tests/` | Vitest tests for tools, resources, services, and config. |
|
|
401
|
+
| `docs/` | Local REST API OpenAPI spec and the generated `tree.md`. |
|
|
423
402
|
| `changelog/` | Per-version release notes; `CHANGELOG.md` is the regenerated rollup. |
|
|
424
403
|
|
|
425
404
|
## Development guide
|
|
@@ -427,13 +406,13 @@ The image binds to `0.0.0.0` inside the container (required for Docker port mapp
|
|
|
427
406
|
See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
|
|
428
407
|
|
|
429
408
|
- Handlers throw, framework catches — no `try/catch` in tool logic
|
|
430
|
-
- Use `ctx.log` for request-scoped logging
|
|
409
|
+
- Use `ctx.log` for request-scoped logging; route every Local REST API call through `getObsidianService()`
|
|
431
410
|
- Register new tools and resources via the barrels in `src/mcp-server/*/definitions/index.ts`
|
|
432
411
|
- Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
|
|
433
412
|
|
|
434
413
|
## Contributing
|
|
435
414
|
|
|
436
|
-
Bugs, feature requests, and documentation gaps belong in an issue
|
|
415
|
+
Bugs, feature requests, and documentation gaps belong in an issue; see [CONTRIBUTING.md](.github/CONTRIBUTING.md) for what makes one actionable and [CODE_OF_CONDUCT.md](.github/CODE_OF_CONDUCT.md) for how we work together. Security reports go through [SECURITY.md](.github/SECURITY.md), never a public issue.
|
|
437
416
|
|
|
438
417
|
Run checks and tests before submitting:
|
|
439
418
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "obsidian_delete_note now deletes only on a round that redeems a server-stored consent record, HTTP startup requires a stateful session, and the server moves to mcp-ts-core 0.13.12, whose tool errors carry a request ID and whose argument hints lead with the field path."
|
|
3
|
+
breaking: true
|
|
4
|
+
security: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 3.6.1 — 2026-10-05
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **HTTP startup requires a stateful session.** `createApp` declares `sessionMode: { default: 'stateful', require: 'stateful' }`: over HTTP, a resolved `stateless` mode (`MCP_SESSION_MODE=stateless`) now fails startup with a `ConfigurationError` instead of serving a delete confirmation no 2025-era client can answer. stdio starts regardless of the variable. Before, stateful was only the default and an explicit `stateless` still started.
|
|
12
|
+
- **`obsidian_delete_note` reads the note once (GET) to build its confirmation prompt**, replacing the size probe (HEAD).
|
|
13
|
+
- **`obsidian_append_to_note` description** now says a line break is inserted before content appended to a file that does not end in one.
|
|
14
|
+
- **A table-patch rejection now reads "the target is not a table"** instead of "the target block is not a table".
|
|
15
|
+
- **Docker:** dependencies install in a `deps` stage pinned to `$BUILDPLATFORM` (OTel peers via `scripts/install-otel.ts`, musl bindings pruned by `scripts/prune-musl-packages.ts`), base images move to Bun 1.4.2, and the image no longer sets `MCP_SESSION_MODE` (the session mode comes from `src/`) or `MCP_FORCE_CONSOLE_LOGGING` (nothing read it).
|
|
16
|
+
- **`server.json`:** npm entries drop the `run start:*` package arguments and set `runtimeHint: "npx"`; the `streamable-http` entry fixes `MCP_TRANSPORT_TYPE` to `http`.
|
|
17
|
+
- **mcp-ts-core 0.13.12:** tool error results carry `data.requestId` and their text closes with `request <id>` (0.13.10); a declared `recovery` hint reaches the client without the handler forwarding it, so the server's 29 `ctx.recoveryFor` forwards are removed with no change to the hints clients receive (0.13.10); argument-rejection hints lead with the field path, e.g. `format: Invalid option: …` (0.13.9); server stack traces (0.13.7) and request context (0.13.8) no longer appear in error `data`.
|
|
18
|
+
- README rewritten; package keywords add `obsidian-md` and `pkm`; `dist/*.mcpb` is excluded from the npm package; `.gitignore`, `.mcpbignore`, `.dockerignore`, and `.env.example` updated; framework skills, scripts, and the CodeQL workflow re-synced.
|
|
19
|
+
|
|
20
|
+
## Security
|
|
21
|
+
|
|
22
|
+
- **`obsidian_delete_note` deletes only on a round that redeems the consent record the server stored when it asked** (operation, caller, resolved path, SHA-256 of the note content). A replayed or pre-supplied confirmation, a made-up `requestState`, or a record for another note, caller, or since-changed content gets a fresh prompt instead of a deletion. Before, an accepted `confirm` answer alone was enough, and a client can send an answer on a call nothing prompted for: mcp-ts-core 0.13.10 limits that to clients that declared elicitation, and the record closes it for them. Records live in `ctx.state` (default in-memory, process-local): a multi-instance HTTP deployment needs a shared storage provider.
|
|
23
|
+
|
|
24
|
+
## Dependencies
|
|
25
|
+
|
|
26
|
+
- `@cyanheads/mcp-ts-core` ^0.13.6 → ^0.13.12
|
|
27
|
+
- `undici` ^8.10.2 → ^8.11.2
|
|
28
|
+
- dev: `@biomejs/biome` ^2.5.14 → ^2.5.15, `@socketsecurity/bun-security-scanner` ^1.1.2 → ^1.1.3, `@types/node` ^26.6.2 → ^26.6.4, `ignore` ^7.0.9 → ^7.0.11, `tsc-alias` ^1.9.5 → ^1.9.7, `vitest` ^5.0.1 → ^5.0.3
|
|
29
|
+
- `marked` stays pinned at `17.0.6` (the Local REST API's markdown-patch engine is on marked 17; marked 18 changes block-token spans)
|