obsidian-mcp-server 3.5.4 → 3.6.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 +9 -3
- package/CLAUDE.md +9 -3
- package/README.md +14 -13
- package/changelog/3.5.x/3.5.5.md +20 -0
- package/changelog/3.6.x/3.6.0.md +31 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/_shared/schemas.js +2 -2
- package/dist/mcp-server/tools/definitions/_shared/schemas.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +97 -61
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +14 -2
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +17 -4
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -1
- 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 +4 -3
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +5 -2
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +6 -3
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +17 -4
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +20 -6
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +6 -5
- 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 +26 -24
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +14 -2
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +26 -13
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -1
- package/dist/services/obsidian/frontmatter-ops.d.ts +7 -4
- package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -1
- package/dist/services/obsidian/frontmatter-ops.js +502 -59
- package/dist/services/obsidian/frontmatter-ops.js.map +1 -1
- package/dist/services/obsidian/markdown-blocks.d.ts +39 -0
- package/dist/services/obsidian/markdown-blocks.d.ts.map +1 -0
- package/dist/services/obsidian/markdown-blocks.js +611 -0
- package/dist/services/obsidian/markdown-blocks.js.map +1 -0
- package/dist/services/obsidian/obsidian-service.d.ts +18 -12
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
- package/dist/services/obsidian/obsidian-service.js +653 -130
- package/dist/services/obsidian/obsidian-service.js.map +1 -1
- package/dist/services/obsidian/patch-instruction.d.ts +141 -0
- package/dist/services/obsidian/patch-instruction.d.ts.map +1 -0
- package/dist/services/obsidian/patch-instruction.js +217 -0
- package/dist/services/obsidian/patch-instruction.js.map +1 -0
- package/dist/services/obsidian/section-extractor.d.ts +109 -6
- package/dist/services/obsidian/section-extractor.d.ts.map +1 -1
- package/dist/services/obsidian/section-extractor.js +364 -87
- package/dist/services/obsidian/section-extractor.js.map +1 -1
- package/dist/services/obsidian/types.d.ts +23 -9
- package/dist/services/obsidian/types.d.ts.map +1 -1
- package/manifest.json +1 -1
- package/package.json +3 -2
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** obsidian-mcp-server
|
|
4
|
-
**Version:** 3.
|
|
4
|
+
**Version:** 3.6.0
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
|
|
@@ -233,11 +233,15 @@ Services that accept `ctx` use the same resolver for parity. The Obsidian servic
|
|
|
233
233
|
```ts
|
|
234
234
|
// inside obsidian-service.ts
|
|
235
235
|
throw notFound(`Not found: ${display}`, data('note_missing'), { cause });
|
|
236
|
-
// where data(reason) does: { path, reason, ...ctx.recoveryFor(reason) }
|
|
236
|
+
// where data(reason) does: { ...callerIdentifier(path), reason, ...ctx.recoveryFor(reason) }
|
|
237
|
+
// — `path` on a note route, `commandId` on /commands/<id>/, no key on routes
|
|
238
|
+
// that carry no caller input (/, /tags/, /commands/, /search/).
|
|
237
239
|
// The upstream body is never spread into `data` — it rides as `cause`, which
|
|
238
240
|
// is non-enumerable and so reaches the log without reaching the client.
|
|
239
241
|
```
|
|
240
242
|
|
|
243
|
+
A `fetch` that rejects before any response is classified inside the attempt (`#send`), so the retry decision sees the typed error: a refused certificate throws `ConfigurationError` `certificate_rejected` on the first attempt, an unreachable plugin throws `ServiceUnavailable` `obsidian_unreachable` and keeps the GET/PUT/DELETE retries. Both carry an inline `recovery.hint` rather than `ctx.recoveryFor`, because they reach every tool and resource — including resources with no `errors[]` — and the operator, not the agent, fixes them.
|
|
244
|
+
|
|
241
245
|
**Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code without a contract): use error factories.
|
|
242
246
|
|
|
243
247
|
```ts
|
|
@@ -262,7 +266,9 @@ src/
|
|
|
262
266
|
services/
|
|
263
267
|
obsidian/
|
|
264
268
|
obsidian-service.ts # Local REST API client (init/accessor pattern)
|
|
265
|
-
frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers
|
|
269
|
+
frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers + inline tag reader
|
|
270
|
+
markdown-blocks.ts # Block structure (code, HTML, math, tables) for inline tag detection
|
|
271
|
+
patch-instruction.ts # markdown-patch 1.x headers / 2.0 instructions, format negotiation, 2.0 map flattening
|
|
266
272
|
section-extractor.ts # Heading/block/frontmatter section extraction
|
|
267
273
|
types.ts # Domain types (NoteJson, NoteTarget, etc.)
|
|
268
274
|
mcp-server/
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** obsidian-mcp-server
|
|
4
|
-
**Version:** 3.
|
|
4
|
+
**Version:** 3.6.0
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
|
|
@@ -233,11 +233,15 @@ Services that accept `ctx` use the same resolver for parity. The Obsidian servic
|
|
|
233
233
|
```ts
|
|
234
234
|
// inside obsidian-service.ts
|
|
235
235
|
throw notFound(`Not found: ${display}`, data('note_missing'), { cause });
|
|
236
|
-
// where data(reason) does: { path, reason, ...ctx.recoveryFor(reason) }
|
|
236
|
+
// where data(reason) does: { ...callerIdentifier(path), reason, ...ctx.recoveryFor(reason) }
|
|
237
|
+
// — `path` on a note route, `commandId` on /commands/<id>/, no key on routes
|
|
238
|
+
// that carry no caller input (/, /tags/, /commands/, /search/).
|
|
237
239
|
// The upstream body is never spread into `data` — it rides as `cause`, which
|
|
238
240
|
// is non-enumerable and so reaches the log without reaching the client.
|
|
239
241
|
```
|
|
240
242
|
|
|
243
|
+
A `fetch` that rejects before any response is classified inside the attempt (`#send`), so the retry decision sees the typed error: a refused certificate throws `ConfigurationError` `certificate_rejected` on the first attempt, an unreachable plugin throws `ServiceUnavailable` `obsidian_unreachable` and keeps the GET/PUT/DELETE retries. Both carry an inline `recovery.hint` rather than `ctx.recoveryFor`, because they reach every tool and resource — including resources with no `errors[]` — and the operator, not the agent, fixes them.
|
|
244
|
+
|
|
241
245
|
**Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code without a contract): use error factories.
|
|
242
246
|
|
|
243
247
|
```ts
|
|
@@ -262,7 +266,9 @@ src/
|
|
|
262
266
|
services/
|
|
263
267
|
obsidian/
|
|
264
268
|
obsidian-service.ts # Local REST API client (init/accessor pattern)
|
|
265
|
-
frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers
|
|
269
|
+
frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers + inline tag reader
|
|
270
|
+
markdown-blocks.ts # Block structure (code, HTML, math, tables) for inline tag detection
|
|
271
|
+
patch-instruction.ts # markdown-patch 1.x headers / 2.0 instructions, format negotiation, 2.0 map flattening
|
|
266
272
|
section-extractor.ts # Heading/block/frontmatter section extraction
|
|
267
273
|
types.ts # Domain types (NoteJson, NoteTarget, etc.)
|
|
268
274
|
mcp-server/
|
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
|
|
|
@@ -60,7 +60,7 @@ Vault-note and tag data are also reachable via tools — `obsidian_get_note` for
|
|
|
60
60
|
|
|
61
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
62
|
- Addressed by vault `path`, the `active` file, or a `periodic` note (`daily` / `weekly` / `monthly` / `quarterly` / `yearly`)
|
|
63
|
-
- Heading sections use `Parent::Child` syntax; a bare leaf name matching several headings returns the first match and lists every colliding path in `candidates`
|
|
63
|
+
- Heading sections use `Parent::Child` syntax and find headings the way the document map does — setext headings (underlined with `===` or `---`) count, `#` lines inside a list item, HTML block, or fence do not — so every heading path the map lists reads back as itself, over the same span a section write edits; 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
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
65
|
- Typed errors include `note_missing`, `path_forbidden`, `no_active_file`, `periodic_unsupported` / `periodic_disabled`, and `path_traversal`
|
|
66
66
|
|
|
@@ -95,8 +95,8 @@ Vault-note and tag data are also reachable via tools — `obsidian_get_note` for
|
|
|
95
95
|
### `obsidian_search_notes` <sub>tool</sub>
|
|
96
96
|
|
|
97
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` — substring
|
|
99
|
-
- Cursor pagination — omit `cursor` for page one, pass `nextCursor` from the prior response; text-mode hits additionally clip to `maxMatchesPerHit` (default 10), flagged with `truncated` / `totalMatches`
|
|
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
100
|
- No dedicated backlinks tool — express "what links here" via `jsonlogic`: `{"regexp": ["\\[\\[Target Note(\\||#|\\]\\])", {"var": "content"}]}`
|
|
101
101
|
|
|
102
102
|
---
|
|
@@ -104,7 +104,7 @@ Vault-note and tag data are also reachable via tools — `obsidian_get_note` for
|
|
|
104
104
|
### `obsidian_write_note` <sub>tool</sub>
|
|
105
105
|
|
|
106
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`
|
|
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
108
|
- Output reports `created`, plus `previousSizeInBytes` / `currentSizeInBytes` on every call to spot an accidental clobber or a mistyped path
|
|
109
109
|
|
|
110
110
|
---
|
|
@@ -112,7 +112,7 @@ Vault-note and tag data are also reachable via tools — `obsidian_get_note` for
|
|
|
112
112
|
### `obsidian_append_to_note` <sub>tool</sub>
|
|
113
113
|
|
|
114
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
|
|
115
|
+
- With `section` — appends to a heading/block/frontmatter target; the file must already exist, and `createTargetIfMissing: true` brings the section itself into existence. On plugin v5.0 and later, content appended to a heading is separated from the section's existing content by a blank line (except a list item appended to a section that ends in a list and has no sub-headings, which continues that list), and a heading in it must sit below the section's own level (`heading_outside_section` otherwise)
|
|
116
116
|
- Block-reference targets concatenate with no separator — include a leading newline in `content` for one
|
|
117
117
|
- `previousSizeInBytes` / `currentSizeInBytes` bracket every call for drift detection
|
|
118
118
|
|
|
@@ -120,9 +120,9 @@ Vault-note and tag data are also reachable via tools — `obsidian_get_note` for
|
|
|
120
120
|
|
|
121
121
|
### `obsidian_patch_note` <sub>tool</sub>
|
|
122
122
|
|
|
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
|
|
125
|
-
- `patchOptions`: `createTargetIfMissing`, `applyIfContentPreexists` (idempotency guard — otherwise `content_preexists`), `trimTargetWhitespace`
|
|
123
|
+
- `operation: "append" | "prepend" | "replace"` against one heading, block reference, or frontmatter field per call; on plugin v5.0 and later, content appended or prepended to a heading is separated from the section's existing content by a blank line (except a list item appended to a section that ends in a list and has no sub-headings, or prepended to one that opens with a list, which continues that list), and a heading in it must sit below the section's own level (`heading_outside_section` otherwise)
|
|
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` (plugin v4.x only; v5.0 and later place the blank lines around inserted content themselves)
|
|
126
126
|
|
|
127
127
|
---
|
|
128
128
|
|
|
@@ -146,7 +146,8 @@ Vault-note and tag data are also reachable via tools — `obsidian_get_note` for
|
|
|
146
146
|
### `obsidian_manage_tags` <sub>tool</sub>
|
|
147
147
|
|
|
148
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
|
|
149
|
+
- Inline detection skips code (fenced, indented, and inline), wikilinks (`[[...]]`), images, a markdown link's destination or label (its text is read), HTML blocks and 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, after another tag (`#a#b` is two tags), or right after markup such as `**`, `_…_`, `==`, `[`, a table cell's `|`, `<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)
|
|
150
151
|
- `add` / `remove` report `applied` vs. `skipped` tags plus the full `tags` set after the change; `list` ignores the input `tags` array
|
|
151
152
|
|
|
152
153
|
---
|
|
@@ -312,7 +313,7 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
|
|
|
312
313
|
### Prerequisites
|
|
313
314
|
|
|
314
315
|
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
315
|
-
- The [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin, **v4.0.0
|
|
316
|
+
- The [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin, **v4.0.0 or later**, installed and enabled in your vault. Generate an API key in **Settings → Community Plugins → Local REST API** and copy it into `OBSIDIAN_API_KEY`. Section-targeted writes and the document map speak markdown-patch 2.0 to plugin v5.0 and later and the 1.x format to v4.x; the server reads the plugin version once and picks the format itself. Two table-row writes (`contentType: "json"`) that markdown-patch 2.0 cannot express go out as 1.x on v5.x too: rows written under a heading, and rows written through a block ID on its own line below the table. Plugin v6.0 removes 1.x, so on v6.0 those two shapes fail; target the table by an ID on its last row instead.
|
|
316
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.
|
|
317
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.
|
|
318
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.
|
|
@@ -349,8 +350,8 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
|
|
|
349
350
|
| Variable | Description | Default |
|
|
350
351
|
|:---------|:------------|:--------|
|
|
351
352
|
| `OBSIDIAN_API_KEY` | **Required.** Bearer token for the Obsidian Local REST API plugin. | — |
|
|
352
|
-
| `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. | `http://127.0.0.1:27123` |
|
|
353
|
-
| `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. | `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` |
|
|
354
355
|
| `OBSIDIAN_REQUEST_TIMEOUT_MS` | Per-request timeout in milliseconds. | `30000` |
|
|
355
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` |
|
|
356
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 |
|
|
@@ -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
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "obsidian_patch_note, obsidian_append_to_note, obsidian_write_note, and the document map now speak markdown-patch 2.0 on Local REST API v5.x, required before plugin v6.0 removes the 1.x format they used to pin."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 3.6.0 — 2026-09-23
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **Section-targeted writes and the document map now speak markdown-patch 2.0**, negotiated once per service from the plugin's `GET /` capability report: a JSON instruction body on Local REST API v5.0 and later, 1.x request headers on v4.x. The supported plugin range widens from v4.0–v5.x to v4.0 and later ([#102](https://github.com/cyanheads/obsidian-mcp-server/issues/102)).
|
|
12
|
+
|
|
13
|
+
## Changed
|
|
14
|
+
|
|
15
|
+
- **On plugin v5.x, content appended or prepended to a heading is separated from the section's existing content by a blank line**, except a list item continuing a list at the section's own edge, which stays tight instead of gaining paragraph spacing on every append ([#145](https://github.com/cyanheads/obsidian-mcp-server/issues/145)).
|
|
16
|
+
- **On plugin v5.x, a heading at or above the target section's own level in written content is rejected** with the new `heading_outside_section` reason instead of silently closing the section early.
|
|
17
|
+
- **A patch refusal that isn't a missing target now reports `patch_rejected`** instead of the misleading `section_target_missing`.
|
|
18
|
+
- **Two table-row writes still go out as markdown-patch 1.x on v5.x** — rows written under a heading, and rows addressed through a standalone `^id` line — since 2.0 cannot express either shape.
|
|
19
|
+
- **A failing `GET /` now fails the section write or document-map read with its classified error** instead of guessing a markdown-patch format.
|
|
20
|
+
- **`patchOptions.trimTargetWhitespace` is honored on plugin v4.x only**; v5.0 and later place the blank lines around inserted content themselves and ignore it.
|
|
21
|
+
|
|
22
|
+
## Fixed
|
|
23
|
+
|
|
24
|
+
- **`obsidian_manage_tags` reads inline tags against a block-structure scan** instead of a flat regex split, so indented code, glued tags (`#a#b`), underscore boundaries (`_#t_`), table cells, narrowed link/fence/HTML-tag grammars, and HTML blocks beyond `<div>`/`<p>` all agree with Obsidian's own metadata cache ([#139](https://github.com/cyanheads/obsidian-mcp-server/issues/139), [#140](https://github.com/cyanheads/obsidian-mcp-server/issues/140), [#144](https://github.com/cyanheads/obsidian-mcp-server/issues/144)).
|
|
25
|
+
- **`obsidian_get_note` `format: "section"` finds headings the way the document map does** — lexed with `marked`, so setext headings count and a `#` line inside a list item or HTML block does not — so a section read now covers exactly the span a write edits. The plugin v4.x repeated-heading check reads the same scan, and `obsidian_write_note` strips a leading setext heading that names the section as it does an ATX one ([#141](https://github.com/cyanheads/obsidian-mcp-server/issues/141)).
|
|
26
|
+
- **On plugin v5.x, a heading write to a CRLF note lands at the section's edge**; the 1.x format placed it mid-line, offset by the note's line endings ([#102](https://github.com/cyanheads/obsidian-mcp-server/issues/102)).
|
|
27
|
+
- **`obsidian_get_note` `format: "document-map"` lists headings in note order when a heading name is integer-like** (`## 2025` above `## 2024`), where the map listed those names first; the `candidates` of an `ambiguous_section` error follow the same order ([#102](https://github.com/cyanheads/obsidian-mcp-server/issues/102)).
|
|
28
|
+
|
|
29
|
+
## Dependencies
|
|
30
|
+
|
|
31
|
+
- `marked` added at `17.0.6` (pinned exact — the heading scan's tokenizer overrides read `marked`'s rule internals, and the pin matches the major the plugin's own markdown-patch lexes with)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/_shared/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,4EAA4E;AAC5E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;2BA4BvB,CAAC;AAEH,yCAAyC;AACzC,eAAO,MAAM,aAAa;;;;;;;iBAUxB,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/_shared/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,4EAA4E;AAC5E,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;2BA4BvB,CAAC;AAEH,yCAAyC;AACzC,eAAO,MAAM,aAAa;;;;;;;iBAUxB,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;kBAmBlB,CAAC;AAEd,eAAO,MAAM,iBAAiB;;;GAK3B,CAAC;AAEJ,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACtD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
|
@@ -43,7 +43,7 @@ export const SectionSchema = z.object({
|
|
|
43
43
|
target: z
|
|
44
44
|
.string()
|
|
45
45
|
.min(1)
|
|
46
|
-
.describe('Heading
|
|
46
|
+
.describe('Heading, block, or frontmatter locator. Heading: the full `Parent::Child` path as `obsidian_get_note` `format: "document-map"` lists it, or a bare leaf name matched at any depth. A leaf shared by several headings reads the first (every full path comes back in `candidates`); a write rejects it with `ambiguous_section`, unless one of them has no parent heading, in which case the write targets that one. A full path that occurs more than once in the note reads the first; a write rejects it with `ambiguous_section`. Block: the reference ID without the caret (e.g. "2d9b4a", not "^2d9b4a"). Frontmatter: the field name.'),
|
|
47
47
|
});
|
|
48
48
|
export const PatchOptionsSchema = z
|
|
49
49
|
.object({
|
|
@@ -58,7 +58,7 @@ export const PatchOptionsSchema = z
|
|
|
58
58
|
trimTargetWhitespace: z
|
|
59
59
|
.boolean()
|
|
60
60
|
.default(false)
|
|
61
|
-
.describe('Trim whitespace from the target section before applying the operation.'),
|
|
61
|
+
.describe('Trim whitespace from the target section before applying the operation. Honored by Local REST API v4.x only; v5.0 and later place the blank lines around inserted content themselves and ignore it.'),
|
|
62
62
|
})
|
|
63
63
|
.optional();
|
|
64
64
|
export const ContentTypeSchema = z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/_shared/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IACvD,CAAC;SACE,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QACnE,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CAAC,kEAAkE,CAAC;KAChF,CAAC;SACD,QAAQ,CAAC,4CAA4C,CAAC;IACzD,CAAC;SACE,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,8CAA8C,CAAC;KACnF,CAAC;SACD,QAAQ,CAAC,gEAAgE,CAAC;IAC7E,CAAC;SACE,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,oDAAoD,CAAC;QAC1F,MAAM,EAAE,CAAC;aACN,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;aAC3D,QAAQ,CAAC,4BAA4B,CAAC;QACzC,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,KAAK,CAAC,qBAAqB,CAAC;aAC5B,QAAQ,EAAE;aACV,QAAQ,CAAC,mDAAmD,CAAC;KACjE,CAAC;SACD,QAAQ,CAAC,6CAA6C,CAAC;CAC3D,CAAC,CAAC;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;SACzC,QAAQ,CAAC,mEAAmE,CAAC;IAChF,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/_shared/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IACvD,CAAC;SACE,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QACnE,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CAAC,kEAAkE,CAAC;KAChF,CAAC;SACD,QAAQ,CAAC,4CAA4C,CAAC;IACzD,CAAC;SACE,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,8CAA8C,CAAC;KACnF,CAAC;SACD,QAAQ,CAAC,gEAAgE,CAAC;IAC7E,CAAC;SACE,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,oDAAoD,CAAC;QAC1F,MAAM,EAAE,CAAC;aACN,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;aAC3D,QAAQ,CAAC,4BAA4B,CAAC;QACzC,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,KAAK,CAAC,qBAAqB,CAAC;aAC5B,QAAQ,EAAE;aACV,QAAQ,CAAC,mDAAmD,CAAC;KACjE,CAAC;SACD,QAAQ,CAAC,6CAA6C,CAAC;CAC3D,CAAC,CAAC;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;SACzC,QAAQ,CAAC,mEAAmE,CAAC;IAChF,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,6mBAA6mB,CAC9mB;CACJ,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC;KAChC,MAAM,CAAC;IACN,qBAAqB,EAAE,CAAC;SACrB,OAAO,EAAE;SACT,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,yEAAyE,CAAC;IACtF,uBAAuB,EAAE,CAAC;SACvB,OAAO,EAAE;SACT,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,iOAAiO,CAClO;IACH,oBAAoB,EAAE,CAAC;SACpB,OAAO,EAAE;SACT,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,oMAAoM,CACrM;CACJ,CAAC;KACD,QAAQ,EAAE,CAAC;AAEd,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;KAC1B,OAAO,CAAC,UAAU,CAAC;KACnB,QAAQ,CACP,qPAAqP,CACtP,CAAC"}
|
|
@@ -311,44 +311,25 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
311
311
|
}>;
|
|
312
312
|
date: import("zod").ZodOptional<import("zod").ZodString>;
|
|
313
313
|
}, import("zod/v4/core").$strip>], "type">;
|
|
314
|
-
|
|
314
|
+
content: import("zod").ZodString;
|
|
315
|
+
section: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
315
316
|
type: import("zod").ZodEnum<{
|
|
316
317
|
block: "block";
|
|
317
318
|
frontmatter: "frontmatter";
|
|
318
319
|
heading: "heading";
|
|
319
320
|
}>;
|
|
320
321
|
target: import("zod").ZodString;
|
|
321
|
-
}, import("zod/v4/core").$strip
|
|
322
|
-
operation: import("zod").ZodEnum<{
|
|
323
|
-
append: "append";
|
|
324
|
-
prepend: "prepend";
|
|
325
|
-
replace: "replace";
|
|
326
|
-
}>;
|
|
327
|
-
content: import("zod").ZodString;
|
|
322
|
+
}, import("zod/v4/core").$strip>>;
|
|
328
323
|
contentType: import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
329
324
|
json: "json";
|
|
330
325
|
markdown: "markdown";
|
|
331
326
|
}>>;
|
|
332
|
-
|
|
333
|
-
createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
334
|
-
applyIfContentPreexists: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
335
|
-
trimTargetWhitespace: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
336
|
-
}, import("zod/v4/core").$strip>>;
|
|
327
|
+
createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
337
328
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
338
329
|
path: import("zod").ZodString;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
frontmatter: "frontmatter";
|
|
343
|
-
heading: "heading";
|
|
344
|
-
}>;
|
|
345
|
-
target: import("zod").ZodString;
|
|
346
|
-
}, import("zod/v4/core").$strip>;
|
|
347
|
-
operation: import("zod").ZodEnum<{
|
|
348
|
-
append: "append";
|
|
349
|
-
prepend: "prepend";
|
|
350
|
-
replace: "replace";
|
|
351
|
-
}>;
|
|
330
|
+
sectionTargeted: import("zod").ZodBoolean;
|
|
331
|
+
sectionTarget: import("zod").ZodOptional<import("zod").ZodString>;
|
|
332
|
+
created: import("zod").ZodBoolean;
|
|
352
333
|
previousSizeInBytes: import("zod").ZodNumber;
|
|
353
334
|
currentSizeInBytes: import("zod").ZodNumber;
|
|
354
335
|
}, import("zod/v4/core").$strip>, readonly [{
|
|
@@ -361,8 +342,8 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
361
342
|
readonly reason: "note_missing";
|
|
362
343
|
readonly thrownBy: "service";
|
|
363
344
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
364
|
-
readonly when: "
|
|
365
|
-
readonly recovery: "Verify the path with obsidian_list_notes or
|
|
345
|
+
readonly when: "Section append targets a path that does not resolve to an existing note (PATCH requires the file to exist).";
|
|
346
|
+
readonly recovery: "Verify the path with obsidian_list_notes, or omit `section` to fall back to whole-file append (which creates the note if missing).";
|
|
366
347
|
}, {
|
|
367
348
|
readonly reason: "no_active_file";
|
|
368
349
|
readonly thrownBy: "service";
|
|
@@ -391,20 +372,32 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
391
372
|
readonly reason: "section_target_missing";
|
|
392
373
|
readonly thrownBy: "service";
|
|
393
374
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
394
|
-
readonly when: "
|
|
395
|
-
readonly recovery: "Call obsidian_get_note with format document-map to discover
|
|
375
|
+
readonly when: "`section` was provided but the named heading/block/frontmatter field does not exist in the note.";
|
|
376
|
+
readonly recovery: "Call obsidian_get_note with format document-map to discover available targets, or pass createTargetIfMissing: true to bring it into existence.";
|
|
396
377
|
}, {
|
|
397
378
|
readonly reason: "ambiguous_section";
|
|
398
379
|
readonly thrownBy: "service";
|
|
399
380
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
|
|
400
|
-
readonly when: "A bare heading leaf name matches more than one heading in the note, so the
|
|
401
|
-
readonly recovery: "Retry with
|
|
381
|
+
readonly when: "A bare heading leaf name matches more than one heading in the note, or the resolved full heading path occurs more than once, so the append target is undetermined.";
|
|
382
|
+
readonly recovery: "Retry with a distinct full Parent::Child heading path from `candidates` on the error data; when every candidate is the same path, rename the repeated headings first.";
|
|
402
383
|
}, {
|
|
403
384
|
readonly reason: "content_preexists";
|
|
404
385
|
readonly thrownBy: "service";
|
|
405
386
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
406
|
-
readonly when: "
|
|
407
|
-
readonly recovery: "
|
|
387
|
+
readonly when: "Section append where the supplied content already appears at the target — rejected to keep retries idempotent (the default for the section path).";
|
|
388
|
+
readonly recovery: "Change the content to something not already present at the target, or use obsidian_patch_note with `patchOptions.applyIfContentPreexists: true` if a duplicate is intended.";
|
|
389
|
+
}, {
|
|
390
|
+
readonly reason: "heading_outside_section";
|
|
391
|
+
readonly thrownBy: "service";
|
|
392
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
393
|
+
readonly when: "On Local REST API v5.0 and later, content appended to a heading carries a heading at or above that section’s own level, which would have to sit outside the section.";
|
|
394
|
+
readonly recovery: "Append the heading at the parent section or at the end of the note (no `section`), or write it one level below the target section or deeper.";
|
|
395
|
+
}, {
|
|
396
|
+
readonly reason: "patch_rejected";
|
|
397
|
+
readonly thrownBy: "service";
|
|
398
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
399
|
+
readonly when: "The section exists but the plugin refused the content for it — table rows for a block that is not a table, a row with the wrong cell count, a frontmatter value that cannot merge, or content of the wrong shape.";
|
|
400
|
+
readonly recovery: "Read the target with obsidian_get_note format section, then send content that fits it — rows matching the table columns, or a value of the field’s own type.";
|
|
408
401
|
}, {
|
|
409
402
|
readonly reason: "path_is_directory";
|
|
410
403
|
readonly thrownBy: "service";
|
|
@@ -434,33 +427,47 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
434
427
|
}>;
|
|
435
428
|
date: import("zod").ZodOptional<import("zod").ZodString>;
|
|
436
429
|
}, import("zod/v4/core").$strip>], "type">;
|
|
437
|
-
|
|
438
|
-
section: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
430
|
+
section: import("zod").ZodObject<{
|
|
439
431
|
type: import("zod").ZodEnum<{
|
|
440
432
|
block: "block";
|
|
441
433
|
frontmatter: "frontmatter";
|
|
442
434
|
heading: "heading";
|
|
443
435
|
}>;
|
|
444
436
|
target: import("zod").ZodString;
|
|
445
|
-
}, import("zod/v4/core").$strip
|
|
437
|
+
}, import("zod/v4/core").$strip>;
|
|
438
|
+
operation: import("zod").ZodEnum<{
|
|
439
|
+
append: "append";
|
|
440
|
+
prepend: "prepend";
|
|
441
|
+
replace: "replace";
|
|
442
|
+
}>;
|
|
443
|
+
content: import("zod").ZodString;
|
|
446
444
|
contentType: import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
447
445
|
json: "json";
|
|
448
446
|
markdown: "markdown";
|
|
449
447
|
}>>;
|
|
450
|
-
|
|
448
|
+
patchOptions: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
449
|
+
createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
450
|
+
applyIfContentPreexists: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
451
|
+
trimTargetWhitespace: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
452
|
+
}, import("zod/v4/core").$strip>>;
|
|
451
453
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
452
454
|
path: import("zod").ZodString;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
455
|
+
section: import("zod").ZodObject<{
|
|
456
|
+
type: import("zod").ZodEnum<{
|
|
457
|
+
block: "block";
|
|
458
|
+
frontmatter: "frontmatter";
|
|
459
|
+
heading: "heading";
|
|
460
|
+
}>;
|
|
461
|
+
target: import("zod").ZodString;
|
|
462
|
+
}, import("zod/v4/core").$strip>;
|
|
463
|
+
operation: import("zod").ZodEnum<{
|
|
464
|
+
append: "append";
|
|
465
|
+
prepend: "prepend";
|
|
466
|
+
replace: "replace";
|
|
467
|
+
}>;
|
|
456
468
|
previousSizeInBytes: import("zod").ZodNumber;
|
|
457
469
|
currentSizeInBytes: import("zod").ZodNumber;
|
|
458
470
|
}, import("zod/v4/core").$strip>, readonly [{
|
|
459
|
-
readonly reason: "file_exists";
|
|
460
|
-
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
|
|
461
|
-
readonly when: "Whole-file write was attempted against an existing note and `overwrite` was not set to `true`.";
|
|
462
|
-
readonly recovery: "Retry with overwrite true or use obsidian_patch_note for in-place edits.";
|
|
463
|
-
}, {
|
|
464
471
|
readonly reason: "path_forbidden";
|
|
465
472
|
readonly thrownBy: "service";
|
|
466
473
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
|
|
@@ -470,8 +477,8 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
470
477
|
readonly reason: "note_missing";
|
|
471
478
|
readonly thrownBy: "service";
|
|
472
479
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
473
|
-
readonly when: "
|
|
474
|
-
readonly recovery: "Verify the path with obsidian_list_notes
|
|
480
|
+
readonly when: "The vault path does not resolve to an existing note.";
|
|
481
|
+
readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
|
|
475
482
|
}, {
|
|
476
483
|
readonly reason: "no_active_file";
|
|
477
484
|
readonly thrownBy: "service";
|
|
@@ -500,14 +507,32 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
500
507
|
readonly reason: "section_target_missing";
|
|
501
508
|
readonly thrownBy: "service";
|
|
502
509
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
503
|
-
readonly when: "
|
|
504
|
-
readonly recovery: "Call obsidian_get_note with format document-map to discover available targets.";
|
|
510
|
+
readonly when: "The named heading/block/frontmatter field does not exist in the note. Use `obsidian_get_note` with `format: \"document-map\"` to discover available targets.";
|
|
511
|
+
readonly recovery: "Call obsidian_get_note with format document-map to discover the available targets.";
|
|
505
512
|
}, {
|
|
506
513
|
readonly reason: "ambiguous_section";
|
|
507
514
|
readonly thrownBy: "service";
|
|
508
515
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
|
|
509
|
-
readonly when: "A bare heading leaf name matches more than one heading in the note, so the
|
|
510
|
-
readonly recovery: "Retry with
|
|
516
|
+
readonly when: "A bare heading leaf name matches more than one heading in the note, or the resolved full heading path occurs more than once, so the write target is undetermined.";
|
|
517
|
+
readonly recovery: "Retry with a distinct full Parent::Child heading path from `candidates` on the error data; when every candidate is the same path, rename the repeated headings first.";
|
|
518
|
+
}, {
|
|
519
|
+
readonly reason: "content_preexists";
|
|
520
|
+
readonly thrownBy: "service";
|
|
521
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
522
|
+
readonly when: "The supplied content already appears at the target — the patch was rejected to keep retries idempotent (the default).";
|
|
523
|
+
readonly recovery: "Pass `patchOptions.applyIfContentPreexists: true` to force-apply over preexisting content, or change the content to something not already present.";
|
|
524
|
+
}, {
|
|
525
|
+
readonly reason: "heading_outside_section";
|
|
526
|
+
readonly thrownBy: "service";
|
|
527
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
528
|
+
readonly when: "On Local REST API v5.0 and later, the content of a heading write carries a heading at or above the target section’s own level, which would have to sit outside the section.";
|
|
529
|
+
readonly recovery: "Append the heading at the parent section or at the end of the note with obsidian_append_to_note, or write it one level below the target section or deeper.";
|
|
530
|
+
}, {
|
|
531
|
+
readonly reason: "patch_rejected";
|
|
532
|
+
readonly thrownBy: "service";
|
|
533
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
534
|
+
readonly when: "The target exists but the plugin refused the content for it — table rows for a block that is not a table, a row with the wrong cell count, a frontmatter value that cannot merge, or content of the wrong shape.";
|
|
535
|
+
readonly recovery: "Read the target with obsidian_get_note format section, then send content that fits it — rows matching the table columns, or a value of the field’s own type.";
|
|
511
536
|
}, {
|
|
512
537
|
readonly reason: "path_is_directory";
|
|
513
538
|
readonly thrownBy: "service";
|
|
@@ -550,7 +575,7 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
550
575
|
json: "json";
|
|
551
576
|
markdown: "markdown";
|
|
552
577
|
}>>;
|
|
553
|
-
|
|
578
|
+
overwrite: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
554
579
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
555
580
|
path: import("zod").ZodString;
|
|
556
581
|
sectionTargeted: import("zod").ZodBoolean;
|
|
@@ -559,6 +584,11 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
559
584
|
previousSizeInBytes: import("zod").ZodNumber;
|
|
560
585
|
currentSizeInBytes: import("zod").ZodNumber;
|
|
561
586
|
}, import("zod/v4/core").$strip>, readonly [{
|
|
587
|
+
readonly reason: "file_exists";
|
|
588
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
|
|
589
|
+
readonly when: "Whole-file write was attempted against an existing note and `overwrite` was not set to `true`.";
|
|
590
|
+
readonly recovery: "Retry with overwrite true or use obsidian_patch_note for in-place edits.";
|
|
591
|
+
}, {
|
|
562
592
|
readonly reason: "path_forbidden";
|
|
563
593
|
readonly thrownBy: "service";
|
|
564
594
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Forbidden;
|
|
@@ -568,8 +598,8 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
568
598
|
readonly reason: "note_missing";
|
|
569
599
|
readonly thrownBy: "service";
|
|
570
600
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
571
|
-
readonly when: "Section
|
|
572
|
-
readonly recovery: "Verify the path with obsidian_list_notes, or omit `section` to fall back to whole-file
|
|
601
|
+
readonly when: "Section replace targets a path that does not resolve to an existing note (PATCH requires the file to exist).";
|
|
602
|
+
readonly recovery: "Verify the path with obsidian_list_notes, or omit `section` to fall back to whole-file write (which creates the note when it is absent).";
|
|
573
603
|
}, {
|
|
574
604
|
readonly reason: "no_active_file";
|
|
575
605
|
readonly thrownBy: "service";
|
|
@@ -599,19 +629,25 @@ export declare const writeToolDefinitions: (import("@cyanheads/mcp-ts-core").Too
|
|
|
599
629
|
readonly thrownBy: "service";
|
|
600
630
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
601
631
|
readonly when: "`section` was provided but the named heading/block/frontmatter field does not exist in the note.";
|
|
602
|
-
readonly recovery: "Call obsidian_get_note with format document-map to discover available targets
|
|
632
|
+
readonly recovery: "Call obsidian_get_note with format document-map to discover available targets.";
|
|
603
633
|
}, {
|
|
604
634
|
readonly reason: "ambiguous_section";
|
|
605
635
|
readonly thrownBy: "service";
|
|
606
636
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
|
|
607
|
-
readonly when: "A bare heading leaf name matches more than one heading in the note, so the
|
|
608
|
-
readonly recovery: "Retry with
|
|
637
|
+
readonly when: "A bare heading leaf name matches more than one heading in the note, or the resolved full heading path occurs more than once, so the replacement target is undetermined.";
|
|
638
|
+
readonly recovery: "Retry with a distinct full Parent::Child heading path from `candidates` on the error data; when every candidate is the same path, rename the repeated headings first.";
|
|
609
639
|
}, {
|
|
610
|
-
readonly reason: "
|
|
640
|
+
readonly reason: "heading_outside_section";
|
|
611
641
|
readonly thrownBy: "service";
|
|
612
642
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
613
|
-
readonly when: "
|
|
614
|
-
readonly recovery: "
|
|
643
|
+
readonly when: "On Local REST API v5.0 and later, the new body of a heading section carries a heading at or above that section’s own level, which would have to sit outside the section.";
|
|
644
|
+
readonly recovery: "Append the heading at the parent section or at the end of the note with obsidian_append_to_note, or write it one level below the target section or deeper.";
|
|
645
|
+
}, {
|
|
646
|
+
readonly reason: "patch_rejected";
|
|
647
|
+
readonly thrownBy: "service";
|
|
648
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
649
|
+
readonly when: "The section exists but the plugin refused the new content for it — table rows for a block that is not a table, a row with the wrong cell count, or content of the wrong shape.";
|
|
650
|
+
readonly recovery: "Read the target with obsidian_get_note format section, then send content that fits it — rows matching the table columns, or a value of the field’s own type.";
|
|
615
651
|
}, {
|
|
616
652
|
readonly reason: "path_is_directory";
|
|
617
653
|
readonly thrownBy: "service";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAK/B,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,oBAAoB
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAK/B,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQhC,CAAC;AAEF,mHAAmH;AACnH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAiD,CAAC"}
|
|
@@ -92,14 +92,26 @@ export declare const obsidianAppendToNote: import("@cyanheads/mcp-ts-core").Tool
|
|
|
92
92
|
readonly reason: "ambiguous_section";
|
|
93
93
|
readonly thrownBy: "service";
|
|
94
94
|
readonly code: JsonRpcErrorCode.Conflict;
|
|
95
|
-
readonly when: "A bare heading leaf name matches more than one heading in the note, so the append target is undetermined.";
|
|
96
|
-
readonly recovery: "Retry with
|
|
95
|
+
readonly when: "A bare heading leaf name matches more than one heading in the note, or the resolved full heading path occurs more than once, so the append target is undetermined.";
|
|
96
|
+
readonly recovery: "Retry with a distinct full Parent::Child heading path from `candidates` on the error data; when every candidate is the same path, rename the repeated headings first.";
|
|
97
97
|
}, {
|
|
98
98
|
readonly reason: "content_preexists";
|
|
99
99
|
readonly thrownBy: "service";
|
|
100
100
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
101
101
|
readonly when: "Section append where the supplied content already appears at the target — rejected to keep retries idempotent (the default for the section path).";
|
|
102
102
|
readonly recovery: "Change the content to something not already present at the target, or use obsidian_patch_note with `patchOptions.applyIfContentPreexists: true` if a duplicate is intended.";
|
|
103
|
+
}, {
|
|
104
|
+
readonly reason: "heading_outside_section";
|
|
105
|
+
readonly thrownBy: "service";
|
|
106
|
+
readonly code: JsonRpcErrorCode.ValidationError;
|
|
107
|
+
readonly when: "On Local REST API v5.0 and later, content appended to a heading carries a heading at or above that section’s own level, which would have to sit outside the section.";
|
|
108
|
+
readonly recovery: "Append the heading at the parent section or at the end of the note (no `section`), or write it one level below the target section or deeper.";
|
|
109
|
+
}, {
|
|
110
|
+
readonly reason: "patch_rejected";
|
|
111
|
+
readonly thrownBy: "service";
|
|
112
|
+
readonly code: JsonRpcErrorCode.ValidationError;
|
|
113
|
+
readonly when: "The section exists but the plugin refused the content for it — table rows for a block that is not a table, a row with the wrong cell count, a frontmatter value that cannot merge, or content of the wrong shape.";
|
|
114
|
+
readonly recovery: "Read the target with obsidian_get_note format section, then send content that fits it — rows matching the table columns, or a value of the field’s own type.";
|
|
103
115
|
}, {
|
|
104
116
|
readonly reason: "path_is_directory";
|
|
105
117
|
readonly thrownBy: "service";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"obsidian-append-to-note.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-append-to-note.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAIjE,eAAO,MAAM,oBAAoB
|
|
1
|
+
{"version":3,"file":"obsidian-append-to-note.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-append-to-note.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAIjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkN/B,CAAC"}
|