obsidian-mcp-server 3.7.0 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/AGENTS.md +15 -7
  2. package/CLAUDE.md +15 -7
  3. package/Dockerfile +3 -3
  4. package/README.md +9 -6
  5. package/changelog/3.7.x/3.7.1.md +40 -0
  6. package/dist/config/server-config.js +1 -1
  7. package/dist/config/server-config.js.map +1 -1
  8. package/dist/index.js +13 -32
  9. package/dist/index.js.map +1 -1
  10. package/dist/instructions.d.ts +20 -0
  11. package/dist/instructions.d.ts.map +1 -0
  12. package/dist/instructions.js +40 -0
  13. package/dist/instructions.js.map +1 -0
  14. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +39 -0
  15. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +60 -1
  17. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/index.d.ts +127 -116
  19. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +6 -0
  21. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +12 -4
  23. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +3 -3
  25. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
  27. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +20 -16
  28. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
  29. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
  30. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +6 -4
  31. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -1
  32. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +4 -2
  34. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -1
  35. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +2 -2
  36. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -1
  37. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
  38. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +4 -2
  39. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -1
  40. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +26 -2
  41. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -1
  42. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +46 -14
  43. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
  44. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +5 -0
  45. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
  46. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +21 -4
  47. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -1
  48. package/dist/services/obsidian/frontmatter-ops.d.ts +15 -1
  49. package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -1
  50. package/dist/services/obsidian/frontmatter-ops.js +55 -10
  51. package/dist/services/obsidian/frontmatter-ops.js.map +1 -1
  52. package/dist/services/obsidian/jsonlogic-evaluator.d.ts +83 -0
  53. package/dist/services/obsidian/jsonlogic-evaluator.d.ts.map +1 -0
  54. package/dist/services/obsidian/jsonlogic-evaluator.js +192 -0
  55. package/dist/services/obsidian/jsonlogic-evaluator.js.map +1 -0
  56. package/dist/services/obsidian/jsonlogic-worker.d.ts +53 -0
  57. package/dist/services/obsidian/jsonlogic-worker.d.ts.map +1 -0
  58. package/dist/services/obsidian/jsonlogic-worker.js +237 -0
  59. package/dist/services/obsidian/jsonlogic-worker.js.map +1 -0
  60. package/dist/services/obsidian/obsidian-service.d.ts +56 -8
  61. package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
  62. package/dist/services/obsidian/obsidian-service.js +254 -48
  63. package/dist/services/obsidian/obsidian-service.js.map +1 -1
  64. package/dist/services/obsidian/path-policy.d.ts +33 -5
  65. package/dist/services/obsidian/path-policy.d.ts.map +1 -1
  66. package/dist/services/obsidian/path-policy.js +99 -17
  67. package/dist/services/obsidian/path-policy.js.map +1 -1
  68. package/dist/services/obsidian/types.d.ts +12 -3
  69. package/dist/services/obsidian/types.d.ts.map +1 -1
  70. package/manifest.json +2 -2
  71. package/package.json +18 -14
  72. package/server.json +5 -5
package/AGENTS.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** obsidian-mcp-server
4
- **Version:** 3.7.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.12`
4
+ **Version:** 3.7.1
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0
8
- **Zod:** ^4.6.5
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
8
+ **Zod:** ^4.6.7
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -39,7 +39,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
39
39
  - **Secrets in env vars only.** `OBSIDIAN_API_KEY` is required; never hardcoded.
40
40
  - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
41
41
  - **Command-palette tools are opt-in.** `obsidian_list_commands` and `obsidian_execute_command` are callable only when `OBSIDIAN_ENABLE_COMMANDS=true` — Obsidian commands are opaque and can be destructive. When the flag is unset, the entry point wraps both with `disabledTool()` so they're absent from `tools/list` (LLM can't invoke) but visible in the operator-facing manifest with a hint to enable them.
42
- - **Path-policy gating goes through `PathPolicy`.** Every path-taking method on `ObsidianService` calls `policy.assertReadable` / `assertWritable` before the upstream HTTP call; `listFiles` gates with `assertListable` and filters entries with `filterListing` (readable entries plus folders on the way to the scope), so every listing consumer inherits the scope; `obsidian_search_notes` post-filters hits via `svc.policy.filterReadable`, and `listTags` collects per-note tags through it when `policy.restrictsReads`. Don't bypass this — `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` / `OBSIDIAN_READ_ONLY` are the single chokepoint, and `path_forbidden` is declared on every path-taking tool's `errors[]` contract.
42
+ - **Path-policy gating goes through `PathPolicy`.** Every path-taking method on `ObsidianService` calls `policy.assertReadable` / `assertWritable` before the upstream HTTP call; `listFiles` gates with `assertListable` and filters entries with `filterListing` (readable entries plus folders on the way to the scope), so every listing consumer inherits the scope; `obsidian_search_notes` filters hits via `svc.policy.filterReadable`. Under `policy.restrictsReads`, `searchJsonLogic` never sends the caller's tree upstream: one `/search/` guarded by `readScopePattern` fetches the readable notes' contexts (plus one body fetch per batch of readable notes when the tree names `content`), `filterReadable` gates them, `keepReadable` reduces `links` / `backlinks` / `unresolvedLinks`, and `createJsonLogicEvaluation` runs the tree on a worker thread with the plugin's operators, bounded by depth, a per-note build budget, a per-search kept-result budget, and a deadline (`logic_too_costly`; a note too deep to hand to the worker is `note_too_deep`), so no hit, result, or error reads a note outside the scope. Link targets are still Obsidian's vault-wide resolution, so the reduced link fields can shift when an out-of-scope note appears or disappears (#165); docs state that bound, never a stronger one. `listTags` reads per-note tags from the same readable contexts, with no evaluation. An `active` / `periodic` target is gated on the path it resolves to, under the target's label: `resolveTarget` / `pinTarget` return a `PathTarget` whose `resolvedFrom` the asserts take, so a denial names `(active file)` or `daily note for 2026-10-01`, never a path the caller did not send, and a tool that reads a target then writes it writes to that pinned path rather than resolving the target again. Don't bypass this — `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` / `OBSIDIAN_READ_ONLY` are the single chokepoint, and `path_forbidden` is declared on every path-taking tool's `errors[]` contract.
43
43
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
44
44
 
45
45
  ---
@@ -274,19 +274,25 @@ Available factories: `notFound`, `validationError`, `forbidden`, `unauthorized`,
274
274
  ```text
275
275
  src/
276
276
  index.ts # createApp() entry point — registers tools/resources, inits Obsidian service
277
+ instructions.ts # buildInstructions() — server `instructions`, effective path scope included
277
278
  config/
278
279
  server-config.ts # OBSIDIAN_* env vars (Zod schema)
279
280
  services/
280
281
  obsidian/
281
282
  obsidian-service.ts # Local REST API client (init/accessor pattern)
282
- frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers + inline tag reader
283
+ path-policy.ts # PathPolicy — OBSIDIAN_READ_PATHS / WRITE_PATHS / READ_ONLY gate
284
+ jsonlogic-deps.d.ts # Type declarations for json-logic-js and glob-to-regexp
285
+ jsonlogic-evaluator.ts # Scoped JSONLogic evaluation: bounds, worker hand-off, logic_* errors
286
+ jsonlogic-worker.ts # Worker entry: the plugin's JSONLogic operators, hardened and budgeted
287
+ frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers + inline tag reader + the wikilink span rule (`wikilinkCloser`) `obsidian_get_note` imports
283
288
  markdown-blocks.ts # Block structure (code, HTML, math, tables) for inline tag detection
284
289
  patch-instruction.ts # markdown-patch 1.x headers / 2.0 instructions, format negotiation, 2.0 map flattening
285
290
  section-extractor.ts # Heading/block/frontmatter section extraction
286
- types.ts # Domain types (NoteJson, NoteTarget, etc.)
291
+ types.ts # Domain types (NoteJson, NoteTarget, PathTarget, etc.)
287
292
  mcp-server/
288
293
  tools/definitions/
289
294
  _shared/schemas.ts # Shared TargetSchema + SectionSchema reused across tools
295
+ _shared/suggest-paths.ts # Case-mismatch fallback and suggestions for reads; names the existing note on a `case_variant_exists` write
290
296
  index.ts # read/write/command tool sets + buildSearchNotesTool (Omnisearch-aware) and buildDeleteNoteTool (elicitation mode) factories
291
297
  obsidian-*.tool.ts # 14 tool definitions (12 base + 2 opt-in command-palette pair)
292
298
  resources/definitions/
@@ -381,6 +387,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
381
387
  | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, `user_config` wiring, plugin manifest identity and env contract (run by devcheck) |
382
388
  | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
383
389
  | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
390
+ | `bun run test:coverage` | Run tests with Istanbul coverage |
384
391
  | `bun run start:stdio` | Production mode (stdio) — requires `bun run build` first |
385
392
  | `bun run start:http` | Production mode (HTTP) — requires `bun run build` first |
386
393
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` rollup from `changelog/<minor>.x/*.md` |
@@ -435,6 +442,7 @@ security: false # optional — true ONLY for a source
435
442
  bun publish --access public
436
443
 
437
444
  docker buildx build --platform linux/amd64,linux/arm64 \
445
+ --build-arg APP_VERSION=<version> \
438
446
  -t ghcr.io/cyanheads/obsidian-mcp-server:<version> \
439
447
  -t ghcr.io/cyanheads/obsidian-mcp-server:latest \
440
448
  --push .
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** obsidian-mcp-server
4
- **Version:** 3.7.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.12`
4
+ **Version:** 3.7.1
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0
8
- **Zod:** ^4.6.5
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
8
+ **Zod:** ^4.6.7
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -39,7 +39,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
39
39
  - **Secrets in env vars only.** `OBSIDIAN_API_KEY` is required; never hardcoded.
40
40
  - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
41
41
  - **Command-palette tools are opt-in.** `obsidian_list_commands` and `obsidian_execute_command` are callable only when `OBSIDIAN_ENABLE_COMMANDS=true` — Obsidian commands are opaque and can be destructive. When the flag is unset, the entry point wraps both with `disabledTool()` so they're absent from `tools/list` (LLM can't invoke) but visible in the operator-facing manifest with a hint to enable them.
42
- - **Path-policy gating goes through `PathPolicy`.** Every path-taking method on `ObsidianService` calls `policy.assertReadable` / `assertWritable` before the upstream HTTP call; `listFiles` gates with `assertListable` and filters entries with `filterListing` (readable entries plus folders on the way to the scope), so every listing consumer inherits the scope; `obsidian_search_notes` post-filters hits via `svc.policy.filterReadable`, and `listTags` collects per-note tags through it when `policy.restrictsReads`. Don't bypass this — `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` / `OBSIDIAN_READ_ONLY` are the single chokepoint, and `path_forbidden` is declared on every path-taking tool's `errors[]` contract.
42
+ - **Path-policy gating goes through `PathPolicy`.** Every path-taking method on `ObsidianService` calls `policy.assertReadable` / `assertWritable` before the upstream HTTP call; `listFiles` gates with `assertListable` and filters entries with `filterListing` (readable entries plus folders on the way to the scope), so every listing consumer inherits the scope; `obsidian_search_notes` filters hits via `svc.policy.filterReadable`. Under `policy.restrictsReads`, `searchJsonLogic` never sends the caller's tree upstream: one `/search/` guarded by `readScopePattern` fetches the readable notes' contexts (plus one body fetch per batch of readable notes when the tree names `content`), `filterReadable` gates them, `keepReadable` reduces `links` / `backlinks` / `unresolvedLinks`, and `createJsonLogicEvaluation` runs the tree on a worker thread with the plugin's operators, bounded by depth, a per-note build budget, a per-search kept-result budget, and a deadline (`logic_too_costly`; a note too deep to hand to the worker is `note_too_deep`), so no hit, result, or error reads a note outside the scope. Link targets are still Obsidian's vault-wide resolution, so the reduced link fields can shift when an out-of-scope note appears or disappears (#165); docs state that bound, never a stronger one. `listTags` reads per-note tags from the same readable contexts, with no evaluation. An `active` / `periodic` target is gated on the path it resolves to, under the target's label: `resolveTarget` / `pinTarget` return a `PathTarget` whose `resolvedFrom` the asserts take, so a denial names `(active file)` or `daily note for 2026-10-01`, never a path the caller did not send, and a tool that reads a target then writes it writes to that pinned path rather than resolving the target again. Don't bypass this — `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` / `OBSIDIAN_READ_ONLY` are the single chokepoint, and `path_forbidden` is declared on every path-taking tool's `errors[]` contract.
43
43
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
44
44
 
45
45
  ---
@@ -274,19 +274,25 @@ Available factories: `notFound`, `validationError`, `forbidden`, `unauthorized`,
274
274
  ```text
275
275
  src/
276
276
  index.ts # createApp() entry point — registers tools/resources, inits Obsidian service
277
+ instructions.ts # buildInstructions() — server `instructions`, effective path scope included
277
278
  config/
278
279
  server-config.ts # OBSIDIAN_* env vars (Zod schema)
279
280
  services/
280
281
  obsidian/
281
282
  obsidian-service.ts # Local REST API client (init/accessor pattern)
282
- frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers + inline tag reader
283
+ path-policy.ts # PathPolicy — OBSIDIAN_READ_PATHS / WRITE_PATHS / READ_ONLY gate
284
+ jsonlogic-deps.d.ts # Type declarations for json-logic-js and glob-to-regexp
285
+ jsonlogic-evaluator.ts # Scoped JSONLogic evaluation: bounds, worker hand-off, logic_* errors
286
+ jsonlogic-worker.ts # Worker entry: the plugin's JSONLogic operators, hardened and budgeted
287
+ frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers + inline tag reader + the wikilink span rule (`wikilinkCloser`) `obsidian_get_note` imports
283
288
  markdown-blocks.ts # Block structure (code, HTML, math, tables) for inline tag detection
284
289
  patch-instruction.ts # markdown-patch 1.x headers / 2.0 instructions, format negotiation, 2.0 map flattening
285
290
  section-extractor.ts # Heading/block/frontmatter section extraction
286
- types.ts # Domain types (NoteJson, NoteTarget, etc.)
291
+ types.ts # Domain types (NoteJson, NoteTarget, PathTarget, etc.)
287
292
  mcp-server/
288
293
  tools/definitions/
289
294
  _shared/schemas.ts # Shared TargetSchema + SectionSchema reused across tools
295
+ _shared/suggest-paths.ts # Case-mismatch fallback and suggestions for reads; names the existing note on a `case_variant_exists` write
290
296
  index.ts # read/write/command tool sets + buildSearchNotesTool (Omnisearch-aware) and buildDeleteNoteTool (elicitation mode) factories
291
297
  obsidian-*.tool.ts # 14 tool definitions (12 base + 2 opt-in command-palette pair)
292
298
  resources/definitions/
@@ -381,6 +387,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
381
387
  | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, `user_config` wiring, plugin manifest identity and env contract (run by devcheck) |
382
388
  | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
383
389
  | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
390
+ | `bun run test:coverage` | Run tests with Istanbul coverage |
384
391
  | `bun run start:stdio` | Production mode (stdio) — requires `bun run build` first |
385
392
  | `bun run start:http` | Production mode (HTTP) — requires `bun run build` first |
386
393
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` rollup from `changelog/<minor>.x/*.md` |
@@ -435,6 +442,7 @@ security: false # optional — true ONLY for a source
435
442
  bun publish --access public
436
443
 
437
444
  docker buildx build --platform linux/amd64,linux/arm64 \
445
+ --build-arg APP_VERSION=<version> \
438
446
  -t ghcr.io/cyanheads/obsidian-mcp-server:<version> \
439
447
  -t ghcr.io/cyanheads/obsidian-mcp-server:latest \
440
448
  --push .
package/Dockerfile CHANGED
@@ -14,7 +14,7 @@
14
14
  # output. A stage that compiles a native addon needs the target-arch toolchain
15
15
  # and cannot cross-compile this way — drop the flag there.
16
16
  # ==============================================================================
17
- FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS build
17
+ FROM --platform=$BUILDPLATFORM oven/bun:1.4.4 AS build
18
18
 
19
19
  WORKDIR /usr/src/app
20
20
 
@@ -46,7 +46,7 @@ RUN bun run build
46
46
  # A clean image rather than `FROM build`: the build stage's node_modules holds
47
47
  # devDependencies.
48
48
  # ==============================================================================
49
- FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS deps
49
+ FROM --platform=$BUILDPLATFORM oven/bun:1.4.4 AS deps
50
50
 
51
51
  WORKDIR /usr/src/app
52
52
 
@@ -113,7 +113,7 @@ RUN rm -rf node_modules/@socketsecurity/bun-security-scanner
113
113
  # dependencies and build artifacts. Its only Bun invocations are HEALTHCHECK
114
114
  # and CMD, which run on the real target at container start.
115
115
  # ==============================================================================
116
- FROM oven/bun:1.4.2-slim AS production
116
+ FROM oven/bun:1.4.4-slim AS production
117
117
 
118
118
  WORKDIR /usr/src/app
119
119
 
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-3.7.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.2.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/obsidian-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/obsidian-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.2%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-3.7.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.3.1-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/obsidian-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/obsidian-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.4%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -23,7 +23,7 @@
23
23
 
24
24
  ## Overview
25
25
 
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.
26
+ Your Obsidian vault, through the Local REST API plugin. Read, search, and write notes, edit a single heading, block, or frontmatter field in place, and manage tags, with folder-scoped read/write permissions built in.
27
27
 
28
28
  ### Tools
29
29
 
@@ -82,7 +82,7 @@ Note and tag data are also reachable through tools (`obsidian_get_note`, `obsidi
82
82
 
83
83
  ### `obsidian_search_notes` <sub>tool</sub>
84
84
 
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]`
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` (plus `links`, `backlinks`, and `unresolvedLinks` on plugin versions that provide them), with `glob` / `regexp` taking `[PATTERN, VALUE]`
86
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
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`
88
88
 
@@ -91,6 +91,7 @@ Note and tag data are also reachable through tools (`obsidian_get_note`, `obsidi
91
91
  ### `obsidian_write_note` <sub>tool</sub>
92
92
 
93
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
+ - On a case-insensitive filesystem (macOS, Windows), a whole-file write to a path that differs from an existing note only in letter case fails with `case_variant_exists` and writes nothing; `existingPath` names that note when the parent folder's listing shows exactly one case match, and is omitted when several match or when a file Obsidian does not index (a dot-file) sits at the path
94
95
  - With `section`, replaces only that heading, block, or frontmatter field and keeps the heading line; output reports `created`, `sectionTargeted`, and the resolved `sectionTarget`
95
96
 
96
97
  ---
@@ -99,6 +100,7 @@ Note and tag data are also reachable through tools (`obsidian_get_note`, `obsidi
99
100
 
100
101
  - 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
102
  - 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
103
+ - A whole-file append to a path that differs from an existing note only in letter case fails with `case_variant_exists` on a case-insensitive filesystem, with `existingPath` naming that note when the parent folder's listing shows exactly one case match
102
104
 
103
105
  ---
104
106
 
@@ -322,7 +324,7 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
322
324
  | `OBSIDIAN_REQUEST_TIMEOUT_MS` | Per-request timeout in milliseconds. | `30000` |
323
325
  | `OBSIDIAN_ENABLE_COMMANDS` | Enable `obsidian_list_commands` and `obsidian_execute_command`. Commands are opaque and can be destructive. | `false` |
324
326
  | `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) |
327
+ | `OBSIDIAN_WRITE_PATHS` | Comma-separated folder allowlist for writes. See [Path policy](#path-policy). | unset (the read scope, or the full vault without one) |
326
328
  | `OBSIDIAN_READ_ONLY` | Deny every write and disable the command-palette pair. | `false` |
327
329
  | `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
330
  | `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 |
@@ -348,9 +350,10 @@ Three optional env vars limit which vault paths the tools can touch. Unset, read
348
350
 
349
351
  - Matching is by prefix, recursive, and case-insensitive; trailing slashes are normalized. Write paths are also readable.
350
352
  - `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.
353
+ - A denial fails with `path_forbidden`, echoing the active scope in `data.activeScope` and the recovery hint. A denial on an `active` or `periodic` target names the target (`(active file)`, `daily note for 2026-10-01`), not the note it resolved to. `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.
354
+ - Under `OBSIDIAN_READ_PATHS`, searches return readable notes only, and a JSONLogic tree never reads a note outside the scope: the server evaluates it over the readable notes, with `links`, `backlinks`, and `unresolvedLinks` holding readable entries only. Which note a link points to is still Obsidian's resolution across the whole vault, so those three fields can change when a note outside the scope is created or removed; match link text in `content` when that matters. That evaluation is bounded — 1,000 levels of nesting, a work budget per note for the values a tree builds and one per search for the results it keeps, 10 s — and a tree past a bound fails with `logic_too_costly`. A readable note whose data is nested too deep to hand to the evaluator (about 3,500 levels under Node) fails the search with `note_too_deep`. Without a read scope the tree goes to the plugin unchanged.
352
355
  - 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.
356
+ - The server `instructions` state the effective scope: readable is the read paths plus the write paths, and writable is the write paths, or the read scope when only reads are restricted. The startup log prints the configured lists.
354
357
 
355
358
  ## Running the server
356
359
 
@@ -0,0 +1,40 @@
1
+ ---
2
+ summary: "Read-scope fixes: under OBSIDIAN_READ_PATHS, JSONLogic search runs in the server over readable notes only, active and periodic denials name the target rather than the resolved note, read-then-write tools write to the note they read, and instructions state the effective scope. A case-variant write fails as case_variant_exists."
3
+ breaking: false
4
+ security: true
5
+ ---
6
+
7
+ # 3.7.1 — 2026-10-24
8
+
9
+ ## Changed
10
+
11
+ - **The `.mcpb` bundle carries only runtime files and package metadata** ([#166](https://github.com/cyanheads/obsidian-mcp-server/issues/166)). `src/`, `tests/`, `scripts/`, `docs/`, CI and editor config, and agent files are left out.
12
+
13
+ ## Fixed
14
+
15
+ - **A whole-file `obsidian_write_note` or `obsidian_append_to_note` to a path that differs from an existing note only in letter case fails `case_variant_exists`** ([#159](https://github.com/cyanheads/obsidian-mcp-server/issues/159)). Conflict, with `existingPath` when the folder listing shows exactly one case match; it replaces a retried `ServiceUnavailable`. Nothing is written.
16
+ - **`obsidian_manage_tags` reads a wikilink's extent as Obsidian does** ([#160](https://github.com/cyanheads/obsidian-mcp-server/issues/160)). A `#word` inside `[[Note|a #b [1]]]` is link text, not an inline tag.
17
+ - **The server `instructions` state the effective scope** ([#158](https://github.com/cyanheads/obsidian-mcp-server/issues/158)): readable is the read paths plus the write paths, and writable is the write paths, or the read scope when only reads are restricted. Under `OBSIDIAN_READ_ONLY=true` with `OBSIDIAN_READ_PATHS`, the read scope is stated.
18
+
19
+ ## Security
20
+
21
+ - **Under `OBSIDIAN_READ_PATHS`, `obsidian_search_notes` in `jsonlogic` mode evaluates the tree in the server over readable notes only** ([#156](https://github.com/cyanheads/obsidian-mcp-server/issues/156)). No hit, result, or error reads a note outside the scope, and `links`, `backlinks`, and `unresolvedLinks` hold readable entries. Which note a link resolves to is still Obsidian's vault-wide resolution ([#165](https://github.com/cyanheads/obsidian-mcp-server/issues/165)). Evaluation is bounded: a tree past a bound fails `logic_too_costly`, and a readable note whose data is nested too deep to hand to the evaluator fails `note_too_deep`. Without a read scope the tree goes to the plugin unchanged.
22
+ - **A `path_forbidden` denial on an `active` or `periodic` target names the target** ([#157](https://github.com/cyanheads/obsidian-mcp-server/issues/157)), such as `(active file)` or `daily note for 2026-10-01`, not the note it resolved to.
23
+ - **`obsidian_replace_in_note`, `obsidian_manage_frontmatter` (`delete`), and `obsidian_manage_tags` (`add`, `remove`) write to the path their read resolved** ([#162](https://github.com/cyanheads/obsidian-mcp-server/issues/162)), so a change of active note or period between the read and the write cannot redirect the write to another note. `obsidian_manage_tags` reads back that same path, and `obsidian_get_note` `format: "document-map"` on an `active` or `periodic` target resolves it once, so the map and the returned `path` describe the same note.
24
+
25
+ ## Dependencies
26
+
27
+ - `@cyanheads/mcp-ts-core` ^0.13.12 → ^0.13.15
28
+ - `js-yaml` ^5.4.2 → ^5.5.0
29
+ - `pino-pretty` ^13.1.3 → ^13.2.0
30
+ - `undici` ^8.11.2 → ^8.12.3
31
+ - `zod` ^4.6.5 → ^4.6.7
32
+ - `json-logic-js` 2.0.5 (new; the plugin's own JSONLogic engine, evaluated in-server under a read scope)
33
+ - `glob-to-regexp` 0.4.1 (new; the plugin's `glob` operator)
34
+ - dev: `@biomejs/biome` ^2.5.15 → ^2.6.2
35
+ - dev: `@types/node` ^26.6.4 → ^26.6.5
36
+ - dev: `ignore` ^7.0.12 → ^7.0.13
37
+ - dev: `tsc-alias` ^1.9.7 → ^1.9.8
38
+ - dev: `vitest` ^5.0.3 → ^5.0.5
39
+ - dev: `@vitest/coverage-istanbul` ^5.0.5 (new, `test:coverage` script)
40
+ - toolchain: Bun 1.4.2 → 1.4.4 (`packageManager`, Dockerfile, README badge)
@@ -112,7 +112,7 @@ const ServerConfigSchema = z.object({
112
112
  .default(false)
113
113
  .describe('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.'),
114
114
  readPaths: envPathList.describe('Optional vault-relative folder allowlist for read operations. Comma-separated; prefix-based with implicit recursion; case-insensitive; trailing slashes normalized. Unset = full vault.'),
115
- writePaths: envPathList.describe('Optional vault-relative folder allowlist for write operations. Same syntax as OBSIDIAN_READ_PATHS. Write paths are implicitly readable. Unset = full vault.'),
115
+ writePaths: envPathList.describe('Optional vault-relative folder allowlist for write operations. Same syntax as OBSIDIAN_READ_PATHS. Write paths are implicitly readable. Unset = full vault, or the read scope when reads are restricted.'),
116
116
  readOnly: envBoolean
117
117
  .default(false)
118
118
  .describe('Global kill switch. When true, denies every write regardless of OBSIDIAN_WRITE_PATHS, and suppresses the OBSIDIAN_ENABLE_COMMANDS pair (commands can mutate). Defaults to false.'),
@@ -1 +1 @@
1
- {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  import { createApp, disabledTool } from '@cyanheads/mcp-ts-core';
11
11
  import { requestContextService } from '@cyanheads/mcp-ts-core/utils';
12
12
  import { getServerConfig } from './config/server-config.js';
13
+ import { buildInstructions } from './instructions.js';
13
14
  import { allPromptDefinitions } from './mcp-server/prompts/definitions/index.js';
14
15
  import { allResourceDefinitions } from './mcp-server/resources/definitions/index.js';
15
16
  import { buildDeleteNoteTool, buildSearchNotesTool, commandToolDefinitions, readToolDefinitions, writeToolDefinitions, } from './mcp-server/tools/definitions/index.js';
@@ -27,32 +28,6 @@ initObsidianService(config);
27
28
  const obsidian = getObsidianService();
28
29
  const omnisearchReachable = await obsidian.probeOmnisearch();
29
30
  const searchNotesTool = buildSearchNotesTool({ omnisearchReachable });
30
- /**
31
- * Build the server-level `instructions` string sent on every `initialize`.
32
- * Provides baseline orientation about the server and then layers in
33
- * deployment-specific lines (read-only mode, scoped paths, command-palette
34
- * toggle, Omnisearch availability) when those flags are active.
35
- */
36
- function buildInstructions() {
37
- const sections = [
38
- 'Use the `obsidian_*` tools to access the Obsidian vault via the Local REST API plugin: search, read, write, and patch notes, including targeted edits to headings, blocks, and YAML frontmatter. Notes are addressed by vault-relative path including the file extension (e.g. `Folder/Note.md`); tags support hierarchical `parent/child` notation, and counts roll up to parents.',
39
- ];
40
- if (config.readOnly) {
41
- sections.push('Read-only mode is active (OBSIDIAN_READ_ONLY=true): every write tool rejects every path with `path_forbidden` / `read_only_mode`. `obsidian_open_in_ui` still opens notes that exist, but rejects an open against a missing path — Obsidian would create the file.');
42
- }
43
- else if (!policy.isUnrestricted) {
44
- const { readPaths, writePaths } = policy.describe();
45
- const render = (scope) => typeof scope === 'string' ? scope : scope.map((p) => `'${p}'`).join(', ');
46
- sections.push(`Vault path policy is enforced. Readable: ${render(readPaths)}. Writable: ${render(writePaths)}. Paths outside scope reject with \`path_forbidden\` — error data carries the active scope so you can self-correct.`);
47
- }
48
- if (config.enableCommands && !config.readOnly) {
49
- sections.push('Command-palette tools (`obsidian_list_commands`, `obsidian_execute_command`) are enabled and can fire any Obsidian command. Commands are opaque and may be destructive — prefer dedicated tools when one fits.');
50
- }
51
- if (omnisearchReachable) {
52
- sections.push('`obsidian_search_notes` includes an `omnisearch` mode: BM25-ranked, typo-tolerant, with PDF/OCR coverage (via the Text Extractor plugin). Results cap at 50 upstream — narrow the query (quoted phrases, `-exclusion`, `path:` / `ext:` filters) to surface more.');
53
- }
54
- return sections.join('\n\n');
55
- }
56
31
  const writeToolSet = [
57
32
  ...writeToolDefinitions,
58
33
  buildDeleteNoteTool({ elicitation: config.deleteElicitation }),
@@ -86,7 +61,12 @@ const { services } = await createApp({
86
61
  tools,
87
62
  resources: allResourceDefinitions,
88
63
  prompts: allPromptDefinitions,
89
- instructions: buildInstructions(),
64
+ instructions: buildInstructions({
65
+ enableCommands: config.enableCommands,
66
+ omnisearchReachable,
67
+ policy,
68
+ readOnly: config.readOnly,
69
+ }),
90
70
  /**
91
71
  * With `OBSIDIAN_DELETE_ELICITATION=true` (and writes allowed),
92
72
  * `obsidian_delete_note` confirms through `ctx.requestInput`, which a
@@ -106,12 +86,13 @@ const { services } = await createApp({
106
86
  /**
107
87
  * Startup banner — emitted after createApp() returns so the framework's
108
88
  * `logger.initialize()` has run; calls inside `setup()` happen pre-init and
109
- * are dropped. Operators check this against their config to verify the active
110
- * path policy and whether `obsidian_delete_note` asks before deleting
89
+ * are dropped. Operators check this against their config to verify the
90
+ * configured path lists and whether `obsidian_delete_note` asks before deleting
111
91
  * (`deleteElicitation`, false under read-only like `enableCommands`, since
112
- * the tool is disabled there). The active scope on `path_forbidden` errors echoes
113
- * the same data so the LLM (or operator) can self-correct without scrolling
114
- * the log.
92
+ * the tool is disabled there). The server `instructions` and the active scope
93
+ * on `path_forbidden` errors state the effective scope instead (write paths
94
+ * are readable; a write needs read access), so the LLM (or operator) can
95
+ * self-correct without scrolling the log.
115
96
  */
116
97
  const bannerCtx = requestContextService.createRequestContext({
117
98
  operation: 'startup',
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @fileoverview The server-level `instructions` string sent on every
3
+ * `initialize`: baseline orientation, then deployment-specific lines for
4
+ * read-only mode, the effective path scope, the command-palette toggle, and
5
+ * Omnisearch availability.
6
+ * @module instructions
7
+ */
8
+ import type { PathPolicy } from './services/obsidian/path-policy.js';
9
+ /**
10
+ * Build the instructions. The scope lines state what `path_forbidden`
11
+ * enforces (`PathPolicy.effectiveScope`), not the configured lists the
12
+ * startup banner reports.
13
+ */
14
+ export declare function buildInstructions(opts: {
15
+ enableCommands: boolean;
16
+ omnisearchReachable: boolean;
17
+ policy: PathPolicy;
18
+ readOnly: boolean;
19
+ }): string;
20
+ //# sourceMappingURL=instructions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAarE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,cAAc,EAAE,OAAO,CAAC;IACxB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,MAAM,CA6BT"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @fileoverview The server-level `instructions` string sent on every
3
+ * `initialize`: baseline orientation, then deployment-specific lines for
4
+ * read-only mode, the effective path scope, the command-palette toggle, and
5
+ * Omnisearch availability.
6
+ * @module instructions
7
+ */
8
+ const BASELINE = 'Use the `obsidian_*` tools to access the Obsidian vault via the Local REST API plugin: search, read, write, and patch notes, including targeted edits to headings, blocks, and YAML frontmatter. Notes are addressed by vault-relative path including the file extension (e.g. `Folder/Note.md`); tags support hierarchical `parent/child` notation, and counts roll up to parents.';
9
+ const SCOPE_RECOVERY = 'Paths outside scope reject with `path_forbidden` — error data carries the active scope so you can self-correct.';
10
+ /** Prefixes as the instructions quote them; `undefined` is an unrestricted op. */
11
+ function renderScope(scope) {
12
+ return scope === undefined ? 'full vault' : scope.map((p) => `'${p}'`).join(', ');
13
+ }
14
+ /**
15
+ * Build the instructions. The scope lines state what `path_forbidden`
16
+ * enforces (`PathPolicy.effectiveScope`), not the configured lists the
17
+ * startup banner reports.
18
+ */
19
+ export function buildInstructions(opts) {
20
+ const { enableCommands, omnisearchReachable, policy, readOnly } = opts;
21
+ const { readable, writable } = policy.effectiveScope();
22
+ const sections = [BASELINE];
23
+ if (readOnly) {
24
+ sections.push('Read-only mode is active (OBSIDIAN_READ_ONLY=true): write tools are unavailable. `obsidian_open_in_ui` still opens notes that exist, but rejects an open against a missing path — Obsidian would create the file.');
25
+ if (readable !== undefined) {
26
+ sections.push(`Vault path policy is enforced. Readable: ${renderScope(readable)}. ${SCOPE_RECOVERY}`);
27
+ }
28
+ }
29
+ else if (!policy.isUnrestricted) {
30
+ sections.push(`Vault path policy is enforced. Readable: ${renderScope(readable)}. Writable: ${renderScope(writable)}. ${SCOPE_RECOVERY}`);
31
+ }
32
+ if (enableCommands && !readOnly) {
33
+ sections.push('Command-palette tools (`obsidian_list_commands`, `obsidian_execute_command`) are enabled and can fire any Obsidian command. Commands are opaque and may be destructive — prefer dedicated tools when one fits.');
34
+ }
35
+ if (omnisearchReachable) {
36
+ sections.push('`obsidian_search_notes` includes an `omnisearch` mode: BM25-ranked, typo-tolerant, with PDF/OCR coverage (via the Text Extractor plugin). Results cap at 50 upstream — narrow the query (quoted phrases, `-exclusion`, `path:` / `ext:` filters) to surface more.');
37
+ }
38
+ return sections.join('\n\n');
39
+ }
40
+ //# sourceMappingURL=instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.js","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,QAAQ,GACZ,qXAAqX,CAAC;AAExX,MAAM,cAAc,GAClB,iHAAiH,CAAC;AAEpH,kFAAkF;AAClF,SAAS,WAAW,CAAC,KAAoC;IACvD,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAKjC;IACC,MAAM,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACvD,MAAM,QAAQ,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5B,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,CACX,mNAAmN,CACpN,CAAC;QACF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CACX,4CAA4C,WAAW,CAAC,QAAQ,CAAC,KAAK,cAAc,EAAE,CACvF,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CACX,4CAA4C,WAAW,CAAC,QAAQ,CAAC,eAAe,WAAW,CAAC,QAAQ,CAAC,KAAK,cAAc,EAAE,CAC3H,CAAC;IACJ,CAAC;IACD,IAAI,cAAc,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CACX,gNAAgN,CACjN,CAAC;IACJ,CAAC;IACD,IAAI,mBAAmB,EAAE,CAAC;QACxB,QAAQ,CAAC,IAAI,CACX,mQAAmQ,CACpQ,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC"}
@@ -16,6 +16,11 @@
16
16
  * NotFound enriched with the candidates in the message and
17
17
  * `error.data.suggestions[]`.
18
18
  *
19
+ * 3. **Case-variant naming on writes.** A whole-file write never substitutes
20
+ * a path, but on a case-insensitive filesystem a write to a case variant of
21
+ * an existing note is refused (`case_variant_exists`); the same listing
22
+ * names the note it collided with as `existingPath`.
23
+ *
19
24
  * `obsidian_get_note` and `obsidian_open_in_ui` wrap their service calls with
20
25
  * `withCaseFallback`; writes and deletes match the exact path.
21
26
  * @module mcp-server/tools/definitions/_shared/suggest-paths
@@ -70,4 +75,38 @@ export declare function discloseCaseSubstitution(ctx: Context, fallback: CaseFal
70
75
  * candidates but never deletes one in place of the requested path).
71
76
  */
72
77
  export declare function findSimilarPaths(ctx: Context, svc: ObsidianService, path: string): Promise<string[]>;
78
+ /** What the parent folder listing shows about the spelling of a path. */
79
+ export interface CaseVariants {
80
+ /** The path itself is listed under exactly that name. */
81
+ exact: boolean;
82
+ /** Listed files whose names equal the path's ignoring case, but not exactly (full vault paths). */
83
+ variants: string[];
84
+ }
85
+ /**
86
+ * Read the parent folder listing for files that differ from `path` only in
87
+ * letter case. A listing that fails, or that the read scope withholds, reads
88
+ * as empty, so no path outside the read scope is ever named; the caller's
89
+ * cancellation propagates as itself.
90
+ */
91
+ export declare function findCaseVariants(ctx: Context, svc: ObsidianService, path: string): Promise<CaseVariants>;
92
+ /**
93
+ * Message and error data for a `case_variant_exists` refusal. `existingPath`
94
+ * is set only when exactly one variant is listed — never picked from several,
95
+ * and never inferred when none is: a file Obsidian does not index (a dot-file)
96
+ * refuses a write the same way and never appears in a listing.
97
+ */
98
+ export declare function caseVariantError(path: string, variants: readonly string[]): {
99
+ message: string;
100
+ data: {
101
+ path: string;
102
+ existingPath?: string;
103
+ };
104
+ };
105
+ /**
106
+ * Run a whole-file write to `path`, and when the service refuses it as
107
+ * `case_variant_exists`, rethrow it naming the existing note from the parent
108
+ * listing. The listing is read only on that failure, so a write that succeeds
109
+ * costs no extra request.
110
+ */
111
+ export declare function withCaseVariantNamed<T>(ctx: Context, svc: ObsidianService, path: string, write: () => Promise<T>): Promise<T>;
73
112
  //# sourceMappingURL=suggest-paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"suggest-paths.d.ts","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/_shared/suggest-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAW/D,uGAAuG;AACvG,MAAM,MAAM,YAAY,CAAC,CAAC,IACtB;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GACtE;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAsC1B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,GAAG,QAAQ,GACxB,MAAM,GAAG,SAAS,CAOpB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB"}
1
+ {"version":3,"file":"suggest-paths.d.ts","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/_shared/suggest-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAW/D,uGAAuG;AACvG,MAAM,MAAM,YAAY,CAAC,CAAC,IACtB;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GACtE;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAsC1B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,GAAG,QAAQ,GACxB,MAAM,GAAG,SAAS,CAOpB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,mGAAmG;IACnG,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAOvB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAYpE;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,CAAC,CAAC,CASZ"}