obsidian-mcp-server 3.6.1 → 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 (104) hide show
  1. package/AGENTS.md +40 -22
  2. package/CLAUDE.md +40 -22
  3. package/Dockerfile +3 -3
  4. package/README.md +29 -17
  5. package/changelog/3.7.x/3.7.0.md +34 -0
  6. package/changelog/3.7.x/3.7.1.md +40 -0
  7. package/dist/config/server-config.d.ts +1 -0
  8. package/dist/config/server-config.d.ts.map +1 -1
  9. package/dist/config/server-config.js +5 -1
  10. package/dist/config/server-config.js.map +1 -1
  11. package/dist/index.js +35 -35
  12. package/dist/index.js.map +1 -1
  13. package/dist/instructions.d.ts +20 -0
  14. package/dist/instructions.d.ts.map +1 -0
  15. package/dist/instructions.js +40 -0
  16. package/dist/instructions.js.map +1 -0
  17. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts +7 -5
  18. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts.map +1 -1
  19. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js +12 -8
  20. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js.map +1 -1
  21. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +69 -9
  22. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -1
  23. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +88 -7
  24. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -1
  25. package/dist/mcp-server/tools/definitions/index.d.ts +256 -309
  26. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  27. package/dist/mcp-server/tools/definitions/index.js +9 -3
  28. package/dist/mcp-server/tools/definitions/index.js.map +1 -1
  29. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +6 -0
  30. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -1
  31. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +12 -4
  32. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -1
  33. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +107 -10
  34. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -1
  35. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +237 -162
  36. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -1
  37. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +1 -0
  38. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
  39. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +70 -24
  40. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
  41. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +6 -1
  42. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
  43. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +8 -10
  44. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
  45. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +7 -5
  46. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -1
  47. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +13 -7
  48. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -1
  49. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +6 -4
  51. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -1
  52. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
  53. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +4 -2
  54. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -1
  55. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +4 -1
  56. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -1
  57. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +15 -4
  58. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -1
  59. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +2 -2
  60. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -1
  61. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
  62. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +4 -2
  63. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -1
  64. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +26 -2
  65. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -1
  66. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +47 -15
  67. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
  68. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +5 -0
  69. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
  70. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +21 -4
  71. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -1
  72. package/dist/services/obsidian/frontmatter-ops.d.ts +15 -1
  73. package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -1
  74. package/dist/services/obsidian/frontmatter-ops.js +55 -10
  75. package/dist/services/obsidian/frontmatter-ops.js.map +1 -1
  76. package/dist/services/obsidian/jsonlogic-evaluator.d.ts +83 -0
  77. package/dist/services/obsidian/jsonlogic-evaluator.d.ts.map +1 -0
  78. package/dist/services/obsidian/jsonlogic-evaluator.js +192 -0
  79. package/dist/services/obsidian/jsonlogic-evaluator.js.map +1 -0
  80. package/dist/services/obsidian/jsonlogic-worker.d.ts +53 -0
  81. package/dist/services/obsidian/jsonlogic-worker.d.ts.map +1 -0
  82. package/dist/services/obsidian/jsonlogic-worker.js +237 -0
  83. package/dist/services/obsidian/jsonlogic-worker.js.map +1 -0
  84. package/dist/services/obsidian/obsidian-service.d.ts +61 -6
  85. package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
  86. package/dist/services/obsidian/obsidian-service.js +301 -53
  87. package/dist/services/obsidian/obsidian-service.js.map +1 -1
  88. package/dist/services/obsidian/patch-instruction.d.ts +20 -1
  89. package/dist/services/obsidian/patch-instruction.d.ts.map +1 -1
  90. package/dist/services/obsidian/patch-instruction.js +30 -0
  91. package/dist/services/obsidian/patch-instruction.js.map +1 -1
  92. package/dist/services/obsidian/path-policy.d.ts +55 -5
  93. package/dist/services/obsidian/path-policy.d.ts.map +1 -1
  94. package/dist/services/obsidian/path-policy.js +143 -19
  95. package/dist/services/obsidian/path-policy.js.map +1 -1
  96. package/dist/services/obsidian/section-extractor.d.ts +24 -0
  97. package/dist/services/obsidian/section-extractor.d.ts.map +1 -1
  98. package/dist/services/obsidian/section-extractor.js +37 -0
  99. package/dist/services/obsidian/section-extractor.js.map +1 -1
  100. package/dist/services/obsidian/types.d.ts +12 -3
  101. package/dist/services/obsidian/types.d.ts.map +1 -1
  102. package/manifest.json +10 -2
  103. package/package.json +18 -14
  104. package/server.json +19 -5
@@ -0,0 +1,34 @@
1
+ ---
2
+ summary: "obsidian_delete_note now deletes on the first call unless OBSIDIAN_DELETE_ELICITATION=true, and obsidian_list_tags and obsidian_list_notes no longer expose tag names or folder and note names outside OBSIDIAN_READ_PATHS."
3
+ breaking: false
4
+ security: true
5
+ ---
6
+
7
+ # 3.7.0 — 2026-10-05
8
+
9
+ ## Added
10
+
11
+ - **`OBSIDIAN_DELETE_ELICITATION`** (default `false`) turns the `obsidian_delete_note` confirmation round on ([#153](https://github.com/cyanheads/obsidian-mcp-server/issues/153)). Declared in `server.json`, `manifest.json`, and `.env.example`; the startup log reports the delete mode.
12
+ - **`requestedPath` and a `notice` on `obsidian_get_note` and `obsidian_open_in_ui`** when a path with no exact match reads or opens its single case-insensitive match instead ([#149](https://github.com/cyanheads/obsidian-mcp-server/issues/149)). Write and delete tools still match the exact path.
13
+
14
+ ## Changed
15
+
16
+ - **`obsidian_delete_note` now deletes on the first call, with no confirmation round** ([#153](https://github.com/cyanheads/obsidian-mcp-server/issues/153)). Set `OBSIDIAN_DELETE_ELICITATION=true` to keep the confirmation; the stateful-session requirement over HTTP then applies only in that mode, and not under `OBSIDIAN_READ_ONLY=true` (which disables the tool), so `MCP_SESSION_MODE=stateless` starts otherwise. `OBSIDIAN_WRITE_PATHS` and `OBSIDIAN_READ_ONLY` bound what a delete reaches.
17
+ - **`obsidian_delete_note` checks write scope before it reads the note or builds a prompt**, so a readable note outside `OBSIDIAN_WRITE_PATHS` fails `path_forbidden` without asking ([#153](https://github.com/cyanheads/obsidian-mcp-server/issues/153)).
18
+ - **`obsidian_search_notes` backlink recipe** also matches `[[Target Note\|Alias]]`, the table-safe alias form.
19
+
20
+ ## Fixed
21
+
22
+ - **`obsidian_get_note` `includeLinks` returns the target Obsidian resolves for `[[Note\|Alias]]`**, with no trailing backslash ([#126](https://github.com/cyanheads/obsidian-mcp-server/issues/126)). Wikilink and markdown-link scanning now follows Obsidian's own shapes (`[[Note|see [1]]]`, `Note(1).md`) and stays linear on unclosed brackets.
23
+ - **Heading writes on Local REST API v4.x keep their line breaks** ([#146](https://github.com/cyanheads/obsidian-mcp-server/issues/146)). An append, prepend, or replace no longer runs into the next heading or the line before. CRLF notes still land mid-line, a plugin limitation.
24
+ - **`obsidian_delete_note` no longer deletes a differently-cased note on a case-insensitive filesystem.** A wrong-case path fails `note_missing`, listing near matches in `suggestions`, and sends no DELETE.
25
+
26
+ ## Security
27
+
28
+ - **`obsidian_list_tags` and `obsidian://tags` list only tags from readable notes when `OBSIDIAN_READ_PATHS` is set** ([#154](https://github.com/cyanheads/obsidian-mcp-server/issues/154)). Before, tag names and vault-wide counts from out-of-scope notes were returned. `count` is now the number of readable notes carrying the tag or a nested tag; write paths and read-only alone leave listings vault-wide.
29
+ - **`obsidian_list_notes` returns only readable entries and the folders leading to the read scope** ([#155](https://github.com/cyanheads/obsidian-mcp-server/issues/155)). Before, out-of-scope folder and root-level note names were listed. A nested scope can now be browsed to from the root, and `truncated` marks only depth-limited folders.
30
+
31
+ ## Dependencies
32
+
33
+ - dev: `ignore` ^7.0.11 → ^7.0.12
34
+ - `marked` stays pinned at `17.0.6` (the Local REST API's markdown-patch engine depends on marked 17; marked 18 parses some blocks differently, which would split section targeting from the plugin's)
@@ -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)
@@ -13,6 +13,7 @@ declare const ServerConfigSchema: z.ZodObject<{
13
13
  readPaths: z.ZodPreprocess<z.ZodPipe<z.ZodOptional<z.ZodArray<z.ZodString>>, z.ZodTransform<string[] | undefined, string[] | undefined>>, unknown>;
14
14
  writePaths: z.ZodPreprocess<z.ZodPipe<z.ZodOptional<z.ZodArray<z.ZodString>>, z.ZodTransform<string[] | undefined, string[] | undefined>>, unknown>;
15
15
  readOnly: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCodec<z.ZodString, z.ZodBoolean>]>>;
16
+ deleteElicitation: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCodec<z.ZodString, z.ZodBoolean>]>>;
16
17
  omnisearchUrl: z.ZodOptional<z.ZodString>;
17
18
  }, z.core.$strip>;
18
19
  export type ServerConfig = z.infer<typeof ServerConfigSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAgF3C,QAAA,MAAM,kBAAkB;;;;;;;;;;iBAuDtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAa9C;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
1
+ {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAgF3C,QAAA,MAAM,kBAAkB;;;;;;;;;;;iBA4DtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAc9C;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -112,10 +112,13 @@ 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.'),
119
+ deleteElicitation: envBoolean
120
+ .default(false)
121
+ .describe('When true, `obsidian_delete_note` asks the user to confirm each delete through an elicitation round, and HTTP startup requires a stateful session (MCP_SESSION_MODE=stateless is refused) unless OBSIDIAN_READ_ONLY=true, which disables the tool. Defaults to false: the delete runs on the first call, bounded by OBSIDIAN_WRITE_PATHS and OBSIDIAN_READ_ONLY.'),
119
122
  omnisearchUrl: z
120
123
  .string()
121
124
  .url()
@@ -133,6 +136,7 @@ export function getServerConfig() {
133
136
  readPaths: 'OBSIDIAN_READ_PATHS',
134
137
  writePaths: 'OBSIDIAN_WRITE_PATHS',
135
138
  readOnly: 'OBSIDIAN_READ_ONLY',
139
+ deleteElicitation: 'OBSIDIAN_DELETE_ELICITATION',
136
140
  omnisearchUrl: 'OBSIDIAN_OMNISEARCH_URL',
137
141
  });
138
142
  return _config;
@@ -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,9 +10,10 @@
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
- import { buildSearchNotesTool, commandToolDefinitions, readToolDefinitions, writeToolDefinitions, } from './mcp-server/tools/definitions/index.js';
16
+ import { buildDeleteNoteTool, buildSearchNotesTool, commandToolDefinitions, readToolDefinitions, writeToolDefinitions, } from './mcp-server/tools/definitions/index.js';
16
17
  import { getObsidianService, initObsidianService } from './services/obsidian/obsidian-service.js';
17
18
  import { PathPolicy } from './services/obsidian/path-policy.js';
18
19
  const config = getServerConfig();
@@ -27,38 +28,22 @@ initObsidianService(config);
27
28
  const obsidian = getObsidianService();
28
29
  const omnisearchReachable = await obsidian.probeOmnisearch();
29
30
  const searchNotesTool = buildSearchNotesTool({ omnisearchReachable });
31
+ const writeToolSet = [
32
+ ...writeToolDefinitions,
33
+ buildDeleteNoteTool({ elicitation: config.deleteElicitation }),
34
+ ];
30
35
  /**
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.
36
+ * The delete confirmation can run only when `obsidian_delete_note` is
37
+ * registered: `OBSIDIAN_READ_ONLY=true` disables it below, so the gate — and
38
+ * the session it needs — exists only with elicitation on and writes allowed.
35
39
  */
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
- }
40
+ const deleteGateActive = config.deleteElicitation && !config.readOnly;
56
41
  const writeTools = config.readOnly
57
- ? writeToolDefinitions.map((def) => disabledTool(def, {
42
+ ? writeToolSet.map((def) => disabledTool(def, {
58
43
  reason: 'Disabled by OBSIDIAN_READ_ONLY=true.',
59
44
  hint: 'Unset OBSIDIAN_READ_ONLY (or set it to false) to enable write tools.',
60
45
  }))
61
- : writeToolDefinitions;
46
+ : writeToolSet;
62
47
  const commandTools = config.enableCommands && !config.readOnly
63
48
  ? commandToolDefinitions
64
49
  : commandToolDefinitions.map((def) => disabledTool(def, {
@@ -76,30 +61,45 @@ const { services } = await createApp({
76
61
  tools,
77
62
  resources: allResourceDefinitions,
78
63
  prompts: allPromptDefinitions,
79
- instructions: buildInstructions(),
64
+ instructions: buildInstructions({
65
+ enableCommands: config.enableCommands,
66
+ omnisearchReachable,
67
+ policy,
68
+ readOnly: config.readOnly,
69
+ }),
80
70
  /**
71
+ * With `OBSIDIAN_DELETE_ELICITATION=true` (and writes allowed),
81
72
  * `obsidian_delete_note` confirms through `ctx.requestInput`, which a
82
73
  * 2025-era HTTP client can only answer on a stateful session — under
83
74
  * stateless HTTP the tool would be unusable for them. Required, not just
84
- * defaulted: over HTTP, a resolved `stateless` mode (an explicit
85
- * `MCP_SESSION_MODE=stateless`) fails startup with a `ConfigurationError`.
86
- * stdio is never refused; `MCP_SESSION_MODE` has no effect there.
75
+ * defaulted, in that mode: over HTTP, a resolved `stateless` mode (an
76
+ * explicit `MCP_SESSION_MODE=stateless`) fails startup with a
77
+ * `ConfigurationError`. With the confirmation off, or the tool disabled by
78
+ * `OBSIDIAN_READ_ONLY=true`, nothing needs a session and `stateful` is only
79
+ * the default. stdio is never refused; `MCP_SESSION_MODE` has no effect there.
87
80
  */
88
- sessionMode: { default: 'stateful', require: 'stateful' },
81
+ sessionMode: deleteGateActive
82
+ ? { default: 'stateful', require: 'stateful' }
83
+ : { default: 'stateful' },
89
84
  teardown: () => obsidian.close(),
90
85
  });
91
86
  /**
92
87
  * Startup banner — emitted after createApp() returns so the framework's
93
88
  * `logger.initialize()` has run; calls inside `setup()` happen pre-init and
94
- * are dropped. Operators check this against their config to verify the active
95
- * path policy. The active scope on `path_forbidden` errors echoes the same
96
- * data so the LLM (or operator) can self-correct without scrolling the log.
89
+ * are dropped. Operators check this against their config to verify the
90
+ * configured path lists and whether `obsidian_delete_note` asks before deleting
91
+ * (`deleteElicitation`, false under read-only like `enableCommands`, since
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.
97
96
  */
98
97
  const bannerCtx = requestContextService.createRequestContext({
99
98
  operation: 'startup',
100
99
  additionalContext: {
101
100
  ...policy.describe(),
102
101
  enableCommands: config.enableCommands && !config.readOnly,
102
+ deleteElicitation: deleteGateActive,
103
103
  omnisearchUrl: obsidian.omnisearchUrl,
104
104
  omnisearchReachable,
105
105
  },
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"}
@@ -2,11 +2,13 @@
2
2
  * @fileoverview obsidian://tags — vault tag listing with usage counts, for
3
3
  * clients that prefer attaching resources.
4
4
  *
5
- * Not a mirror of `obsidian_list_tags`: this is a snapshot of the upstream
6
- * `/tags/` payload, returned whole and in upstream order. The tool shapes the
7
- * same payload for an LLM caller — count-descending, capped, filterable — and
8
- * that shaping deliberately lives in the tool handler rather than the shared
9
- * `ObsidianService.listTags` both call.
5
+ * Not a mirror of `obsidian_list_tags`: this is a snapshot of
6
+ * `ObsidianService.listTags`, returned whole and unsorted — the upstream
7
+ * `/tags/` payload in upstream order, or under OBSIDIAN_READ_PATHS the tags of
8
+ * readable notes in order of first appearance. The tool shapes the same
9
+ * payload for an LLM caller — count-descending, capped, filterable — and that
10
+ * shaping deliberately lives in the tool handler rather than the shared
11
+ * service call both make.
10
12
  * @module mcp-server/resources/definitions/obsidian-tags.resource
11
13
  */
12
14
  import { z } from '@cyanheads/mcp-ts-core';
@@ -1 +1 @@
1
- {"version":3,"file":"obsidian-tags.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-tags.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAGrD,eAAO,MAAM,YAAY;;;;;6BAyBvB,CAAC"}
1
+ {"version":3,"file":"obsidian-tags.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-tags.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAGrD,eAAO,MAAM,YAAY;;;;;6BA+BvB,CAAC"}
@@ -2,18 +2,20 @@
2
2
  * @fileoverview obsidian://tags — vault tag listing with usage counts, for
3
3
  * clients that prefer attaching resources.
4
4
  *
5
- * Not a mirror of `obsidian_list_tags`: this is a snapshot of the upstream
6
- * `/tags/` payload, returned whole and in upstream order. The tool shapes the
7
- * same payload for an LLM caller — count-descending, capped, filterable — and
8
- * that shaping deliberately lives in the tool handler rather than the shared
9
- * `ObsidianService.listTags` both call.
5
+ * Not a mirror of `obsidian_list_tags`: this is a snapshot of
6
+ * `ObsidianService.listTags`, returned whole and unsorted — the upstream
7
+ * `/tags/` payload in upstream order, or under OBSIDIAN_READ_PATHS the tags of
8
+ * readable notes in order of first appearance. The tool shapes the same
9
+ * payload for an LLM caller — count-descending, capped, filterable — and that
10
+ * shaping deliberately lives in the tool handler rather than the shared
11
+ * service call both make.
10
12
  * @module mcp-server/resources/definitions/obsidian-tags.resource
11
13
  */
12
14
  import { resource, z } from '@cyanheads/mcp-ts-core';
13
15
  import { getObsidianService } from '../../../services/obsidian/obsidian-service.js';
14
16
  export const obsidianTags = resource('obsidian://tags', {
15
17
  name: 'obsidian-tags',
16
- description: 'All tags found in the Obsidian vault, with usage counts, in upstream order and uncapped — a full snapshot. Includes hierarchical parents (e.g. `work` for `work/tasks`). Use the `obsidian_list_tags` tool for a count-ranked, capped, filterable view.',
18
+ description: 'All tags found in the Obsidian vault, with usage counts, unsorted and uncapped — a full snapshot. When OBSIDIAN_READ_PATHS is set, only tags carried by readable notes. Includes hierarchical parents (e.g. `work` for `work/tasks`). Use the `obsidian_list_tags` tool for a count-ranked, capped, filterable view.',
17
19
  mimeType: 'application/json',
18
20
  params: z.object({}),
19
21
  output: z.object({
@@ -21,10 +23,12 @@ export const obsidianTags = resource('obsidian://tags', {
21
23
  .array(z
22
24
  .object({
23
25
  name: z.string().describe('Tag name without the leading `#`.'),
24
- count: z.number().describe('Usage count across the vault.'),
26
+ count: z
27
+ .number()
28
+ .describe('Times the tag, or a tag nested under it, occurs across the vault. When OBSIDIAN_READ_PATHS is set: the number of readable notes carrying the tag or a tag nested under it.'),
25
29
  })
26
30
  .describe('A tag with its usage count.'))
27
- .describe('Every tag in the vault, in upstream-provided order.'),
31
+ .describe('Every tag in the vault, in the order the Local REST API reports them. When OBSIDIAN_READ_PATHS is set: every tag carried by a readable note, in order of first appearance.'),
28
32
  }),
29
33
  auth: ['resource:obsidian-tags:read'],
30
34
  async handler(_params, ctx) {
@@ -1 +1 @@
1
- {"version":3,"file":"obsidian-tags.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-tags.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC,iBAAiB,EAAE;IACtD,IAAI,EAAE,eAAe;IACrB,WAAW,EACT,yPAAyP;IAC3P,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,CAAC;aACJ,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;YAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;SAC5D,CAAC;aACD,QAAQ,CAAC,6BAA6B,CAAC,CAC3C;aACA,QAAQ,CAAC,qDAAqD,CAAC;KACnE,CAAC;IACF,IAAI,EAAE,CAAC,6BAA6B,CAAC;IAErC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG;QACxB,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IACvE,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"obsidian-tags.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-tags.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC,iBAAiB,EAAE;IACtD,IAAI,EAAE,eAAe;IACrB,WAAW,EACT,sTAAsT;IACxT,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,CAAC;aACJ,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;YAC9D,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,CACP,4KAA4K,CAC7K;SACJ,CAAC;aACD,QAAQ,CAAC,6BAA6B,CAAC,CAC3C;aACA,QAAQ,CACP,4KAA4K,CAC7K;KACJ,CAAC;IACF,IAAI,EAAE,CAAC,6BAA6B,CAAC;IAErC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG;QACxB,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IACvE,CAAC;CACF,CAAC,CAAC"}
@@ -4,14 +4,23 @@
4
4
  * 1. **Case-insensitive fallback.** If a path lookup 404s, list the parent
5
5
  * directory and look for a single case-insensitive filename match. If
6
6
  * found, retry against the canonical filesystem path (matches v2.x
7
- * behavior). This silently fixes "Readme.md" vs "README.md" on Linux; on
8
- * Mac/Windows the OS already case-folds and the fallback is a no-op.
7
+ * behavior) and report the path that was asked for as `requestedPath`, so
8
+ * the caller can disclose the substitution. The fallback is reachable on
9
+ * every host: the plugin's `note+json` and document-map reads resolve
10
+ * through Obsidian's case-sensitive vault index even on a case-insensitive
11
+ * filesystem (macOS, Windows). Only the raw-markdown read case-folds there,
12
+ * answering 200 for a wrong-case path before any fallback runs.
9
13
  *
10
14
  * 2. **"Did you mean" suggestions.** When no case match exists but the parent
11
15
  * directory has near-matches (e.g., extension-stripped variants), re-throw
12
16
  * NotFound enriched with the candidates in the message and
13
17
  * `error.data.suggestions[]`.
14
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
+ *
15
24
  * `obsidian_get_note` and `obsidian_open_in_ui` wrap their service calls with
16
25
  * `withCaseFallback`; writes and deletes match the exact path.
17
26
  * @module mcp-server/tools/definitions/_shared/suggest-paths
@@ -19,6 +28,16 @@
19
28
  import type { Context } from '@cyanheads/mcp-ts-core';
20
29
  import type { ObsidianService } from '../../../../services/obsidian/obsidian-service.js';
21
30
  import type { NoteTarget } from '../../../../services/obsidian/types.js';
31
+ /** Outcome of {@link withCaseFallback}; `requestedPath` is present only when another path was read. */
32
+ export type CaseFallback<T> = {
33
+ result: T;
34
+ resolvedPath: string | undefined;
35
+ requestedPath?: never;
36
+ } | {
37
+ result: T;
38
+ resolvedPath: string;
39
+ requestedPath: string;
40
+ };
22
41
  /**
23
42
  * Wrap a service call with case-insensitive path fallback and "did you mean"
24
43
  * enrichment. Non-path targets pass through — their paths are resolved
@@ -27,7 +46,7 @@ import type { NoteTarget } from '../../../../services/obsidian/types.js';
27
46
  * For path targets:
28
47
  * - **Exact match** → returns `{ result, resolvedPath: target.path }`.
29
48
  * - **Single case match** → retries with the canonical path and returns
30
- * `{ result, resolvedPath: <canonical> }`.
49
+ * `{ result, resolvedPath: <canonical>, requestedPath: target.path }`.
31
50
  * - **Multiple case matches** → throws `Conflict` with the candidates so the
32
51
  * agent can disambiguate.
33
52
  * - **No case match, extension-stripped near-matches** → throws `NotFound`
@@ -36,17 +55,58 @@ import type { NoteTarget } from '../../../../services/obsidian/types.js';
36
55
  *
37
56
  * `resolvedPath` is `undefined` for non-path targets — callers derive the
38
57
  * canonical path from the result itself (typically `NoteJson.path`).
58
+ * `requestedPath` is present only on the single-case-match branch.
39
59
  */
40
- export declare function withCaseFallback<T>(ctx: Context, svc: ObsidianService, target: NoteTarget, fn: (target: NoteTarget) => Promise<T>): Promise<{
41
- result: T;
42
- resolvedPath: string | undefined;
43
- }>;
60
+ export declare function withCaseFallback<T>(ctx: Context, svc: ObsidianService, target: NoteTarget, fn: (target: NoteTarget) => Promise<T>): Promise<CaseFallback<T>>;
61
+ /**
62
+ * Disclose a `withCaseFallback` substitution on the caller's enrichment: the
63
+ * `requestedPath` field plus a one-line notice naming both paths. `action` is
64
+ * what the tool did with the canonical file. Returns the notice — `notice` is
65
+ * last-wins, so a caller that sets another one must fold this text into it —
66
+ * or `undefined` when nothing was substituted.
67
+ */
68
+ export declare function discloseCaseSubstitution(ctx: Context, fallback: CaseFallback<unknown>, action: 'read' | 'opened'): string | undefined;
44
69
  /**
45
70
  * List the parent directory of `path` and return up to {@link MAX_SUGGESTIONS}
46
71
  * close-match candidates. Match order: case-insensitive equality first, then
47
72
  * extension-stripped equality. Returns `[]` on listing failure or empty
48
- * basename. Used by callers that need suggestions without performing the
49
- * underlying operation (e.g., `obsidian_open_in_ui`'s explicit messaging).
73
+ * basename. Used by callers that need suggestions without substituting a
74
+ * match (e.g., `obsidian_delete_note`'s exact-case miss, which names the
75
+ * candidates but never deletes one in place of the requested path).
50
76
  */
51
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>;
52
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;;;;;;;;;;;;;;;;;GAiBG;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;;;;;;;;;;;;;;;;;GAiBG;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;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,CAsC1D;AAED;;;;;;GAMG;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"}